diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky new file mode 100644 index 00000000000000..e1cbac0528b1f9 --- /dev/null +++ b/.ci/Jenkinsfile_flaky @@ -0,0 +1,131 @@ +#!/bin/groovy + +library 'kibana-pipeline-library' +kibanaLibrary.load() + +// Looks like 'oss:ciGroup:1' or 'oss:firefoxSmoke' +def JOB_PARTS = params.CI_GROUP.split(':') +def IS_XPACK = JOB_PARTS[0] == 'xpack' +def JOB = JOB_PARTS[1] +def CI_GROUP = JOB_PARTS.size() > 2 ? JOB_PARTS[2] : '' +def EXECUTIONS = params.NUMBER_EXECUTIONS.toInteger() +def AGENT_COUNT = getAgentCount(EXECUTIONS) + +def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP) + +def workerFailures = [] + +currentBuild.displayName += trunc(" ${params.GITHUB_OWNER}:${params.branch_specifier}", 24) +currentBuild.description = "${params.CI_GROUP}
Agents: ${AGENT_COUNT}
Executions: ${params.NUMBER_EXECUTIONS}" + +stage("Kibana Pipeline") { + timeout(time: 180, unit: 'MINUTES') { + timestamps { + ansiColor('xterm') { + def agents = [:] + for(def agentNumber = 1; agentNumber <= AGENT_COUNT; agentNumber++) { + def agentNumberInside = agentNumber + def agentExecutions = floor(EXECUTIONS/AGENT_COUNT) + (agentNumber <= EXECUTIONS%AGENT_COUNT ? 1 : 0) + agents["agent-${agentNumber}"] = { + catchError { + print "Agent ${agentNumberInside} - ${agentExecutions} executions" + + kibanaPipeline.withWorkers('flaky-test-runner', { + if (!IS_XPACK) { + kibanaPipeline.buildOss() + if (CI_GROUP == '1') { + runbld "./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh" + } + } else { + kibanaPipeline.buildXpack() + } + }, getWorkerMap(agentNumberInside, agentExecutions, worker, workerFailures))() + } + } + } + + parallel(agents) + + currentBuild.description += ", Failures: ${workerFailures.size()}" + + if (workerFailures.size() > 0) { + print "There were ${workerFailures.size()} test suite failures." + print "The executions that failed were:" + print workerFailures.join("\n") + print "Please check 'Test Result' and 'Pipeline Steps' pages for more info" + } + } + } + } +} + +def getWorkerFromParams(isXpack, job, ciGroup) { + if (!isXpack) { + if (job == 'firefoxSmoke') { + return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }) + } else if(job == 'visualRegression') { + return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }) + } else { + return kibanaPipeline.getOssCiGroupWorker(ciGroup) + } + } + + if (job == 'firefoxSmoke') { + return kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }) + } else if(job == 'visualRegression') { + return kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }) + } else { + return kibanaPipeline.getXpackCiGroupWorker(ciGroup) + } +} + +def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWorkerProcesses = 12) { + def workerMap = [:] + def numberOfWorkers = Math.min(numberOfExecutions, maxWorkerProcesses) + + for(def i = 1; i <= numberOfWorkers; i++) { + def workerExecutions = numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0) + + workerMap["agent-${agentNumber}-worker-${i}"] = { workerNumber -> + for(def j = 0; j < workerExecutions; j++) { + print "Execute agent-${agentNumber} worker-${workerNumber}: ${j}" + withEnv([ + "JOB=agent-${agentNumber}-worker-${workerNumber}-${j}", + "REMOVE_KIBANA_INSTALL_DIR=1", + ]) { + catchError { + try { + worker(workerNumber) + } catch (ex) { + workerFailures << "agent-${agentNumber} worker-${workerNumber}-${j}" + throw ex + } + } + } + } + } + } + + return workerMap +} + +def getAgentCount(executions) { + // Increase agent count every 24 worker processess, up to 3 agents maximum + return Math.min(3, 1 + floor(executions/24)) +} + +def trunc(str, length) { + if (str.size() >= length) { + return str.take(length) + "..." + } + + return str; +} + +// All of the real rounding/truncating methods are sandboxed +def floor(num) { + return num + .toString() + .split('\\.')[0] + .toInteger() +} diff --git a/.ci/jobs.yml b/.ci/jobs.yml index fc3e80064fa6fa..a2d8100f78efd0 100644 --- a/.ci/jobs.yml +++ b/.ci/jobs.yml @@ -14,7 +14,8 @@ JOB: - kibana-ciGroup10 - kibana-ciGroup11 - kibana-ciGroup12 - # - kibana-visualRegression + - kibana-accessibility + - kibana-visualRegression # make sure all x-pack-ciGroups are listed in test/scripts/jenkins_xpack_ci_group.sh - x-pack-firefoxSmoke @@ -28,7 +29,8 @@ JOB: - x-pack-ciGroup8 - x-pack-ciGroup9 - x-pack-ciGroup10 - # - x-pack-visualRegression + - x-pack-accessibility + - x-pack-visualRegression # `~` is yaml for `null` exclude: ~ diff --git a/.ci/run.sh b/.ci/run.sh index 88ce0bd9986a19..9f77438be62d0f 100755 --- a/.ci/run.sh +++ b/.ci/run.sh @@ -21,6 +21,9 @@ kibana-ciGroup*) kibana-visualRegression*) ./test/scripts/jenkins_visual_regression.sh ;; +kibana-accessibility*) + ./test/scripts/jenkins_accessibility.sh + ;; kibana-firefoxSmoke*) ./test/scripts/jenkins_firefox_smoke.sh ;; @@ -34,6 +37,9 @@ x-pack-ciGroup*) x-pack-visualRegression*) ./test/scripts/jenkins_xpack_visual_regression.sh ;; +x-pack-accessibility*) + ./test/scripts/jenkins_xpack_accessibility.sh + ;; x-pack-firefoxSmoke*) ./test/scripts/jenkins_xpack_firefox_smoke.sh ;; diff --git a/.eslintrc.js b/.eslintrc.js index 22d8e67ea702d9..16a80f01278a54 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -55,6 +55,193 @@ module.exports = { extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], overrides: [ + /** + * Temporarily disable some react rules for specific plugins, remove in separate PRs + */ + { + files: ['packages/kbn-ui-framework/**/*.{js,ts,tsx}'], + rules: { + 'jsx-a11y/no-onchange': 'off', + }, + }, + { + files: ['src/core/public/application/**/*.{js,ts,tsx}'], + rules: { + 'react/no-danger': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/console/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/data/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/expressions/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/kbn_vislib_vis_types/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/kibana/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/rules-of-hooks': 'off', + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/tile_map/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/vis_type_markdown/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/vis_type_metric/**/*.{js,ts,tsx}'], + rules: { + 'jsx-a11y/click-events-have-key-events': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/vis_type_table/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/core_plugins/vis_type_vega/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/legacy/ui/public/vis/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/plugins/es_ui_shared/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/plugins/eui_utils/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/plugins/kibana_react/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/rules-of-hooks': 'off', + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['src/plugins/kibana_utils/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/canvas/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/cross_cluster_replication/**/*.{js,ts,tsx}'], + rules: { + 'jsx-a11y/click-events-have-key-events': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/graph/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/index_management/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/rules-of-hooks': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/infra/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/rules-of-hooks': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/lens/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/rules-of-hooks': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/ml/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/monitoring/**/*.{js,ts,tsx}'], + rules: { + 'jsx-a11y/click-events-have-key-events': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/siem/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/rules-of-hooks': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/uptime/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + 'react-hooks/rules-of-hooks': 'off', + }, + }, + { + files: ['x-pack/legacy/plugins/watcher/**/*.{js,ts,tsx}'], + rules: { + 'react-hooks/rules-of-hooks': 'off', + 'react-hooks/exhaustive-deps': 'off', + }, + }, + /** * Prettier */ @@ -175,10 +362,10 @@ module.exports = { '!src/core/server/*.test.mocks.ts', 'src/plugins/**/public/**/*', - '!src/plugins/**/public/index*', + '!src/plugins/**/public/index.{js,ts,tsx}', 'src/plugins/**/server/**/*', - '!src/plugins/**/server/index*', + '!src/plugins/**/server/index.{js,ts,tsx}', ], allowSameFolder: true, }, @@ -213,6 +400,7 @@ module.exports = { 'x-pack/test/functional/apps/**/*.js', 'x-pack/legacy/plugins/apm/**/*.js', 'test/*/config.ts', + 'test/*/{tests,test_suites,apis,apps}/**/*', 'test/visual_regression/tests/**/*', 'x-pack/test/*/{tests,test_suites,apis,apps}/**/*', 'x-pack/test/*/*config.*ts', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4ae7b27a58ee46..94296d076189bf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,8 +8,14 @@ # App Architecture /src/plugins/data/ @elastic/kibana-app-arch -/src/plugins/kibana_utils/ @elastic/kibana-app-arch +/src/plugins/embeddable/ @elastic/kibana-app-arch +/src/plugins/expressions/ @elastic/kibana-app-arch /src/plugins/kibana_react/ @elastic/kibana-app-arch +/src/plugins/kibana_utils/ @elastic/kibana-app-arch +/src/plugins/navigation/ @elastic/kibana-app-arch +/src/plugins/ui_actions/ @elastic/kibana-app-arch +/src/plugins/visualizations/ @elastic/kibana-app-arch +/x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui @@ -36,7 +42,9 @@ /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/ml.ts @elastic/ml-ui -/x-pack/legacy/plugins/transform/ @elastic/ml-ui +# ML team owns the transform plugin, ES team added here for visibility +# because the plugin lives in Kibana's Elasticsearch management section. +/x-pack/legacy/plugins/transform/ @elastic/ml-ui @elastic/es-ui # Operations /renovate.json5 @elastic/kibana-operations @@ -55,11 +63,14 @@ /src/legacy/server/saved_objects/ @elastic/kibana-platform /config/kibana.yml @elastic/kibana-platform /x-pack/plugins/features/ @elastic/kibana-platform +/x-pack/plugins/licensing/ @elastic/kibana-platform # Security /x-pack/legacy/plugins/security/ @elastic/kibana-security /x-pack/legacy/plugins/spaces/ @elastic/kibana-security +/x-pack/plugins/spaces/ @elastic/kibana-security /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security +/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security /src/legacy/server/csp/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security /x-pack/test/api_integration/apis/security/ @elastic/kibana-security @@ -87,9 +98,6 @@ /x-pack/legacy/plugins/rollup/ @elastic/es-ui /x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui -# ML team owns the transform plugin, ES team added here for visibility -# because the plugin lives in Kibana's Elasticsearch management section. -/x-pack/legacy/plugins/transform/ @elastic/es-ui /x-pack/legacy/plugins/watcher/ @elastic/es-ui # Kibana TSVB external contractors diff --git a/.github/labeler.yml b/.github/labeler.yml index 8a776a909cd9e2..57b299912ae9e7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -2,6 +2,7 @@ - src/plugins/data/**/* - src/plugins/embeddable/**/* - src/plugins/kibana_react/**/* +- src/plugins/navigation/**/* - src/plugins/kibana_utils/**/* - src/legacy/core_plugins/dashboard_embeddable_container/**/* - src/legacy/core_plugins/data/**/* diff --git a/.gitignore b/.gitignore index efb5c577746334..02b20da297fc67 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ package-lock.json *.sublime-* npm-debug.log* .tern-project +x-pack/legacy/plugins/apm/tsconfig.json +apm.tsconfig.json diff --git a/.i18nrc.json b/.i18nrc.json index 0ea51fe8212c4a..01065201b9d641 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -1,36 +1,39 @@ { "paths": { "common.ui": "src/legacy/ui", - "data": ["src/legacy/core_plugins/data", "src/plugins/data"], - "expressions": "src/legacy/core_plugins/expressions", - "kibana_react": "src/legacy/core_plugins/kibana_react", - "server": "src/legacy/server", "console": "src/legacy/core_plugins/console", "core": "src/core", + "dashboardEmbeddableContainer": "src/plugins/dashboard_embeddable_container", + "data": ["src/legacy/core_plugins/data", "src/plugins/data"], + "embeddableApi": "src/plugins/embeddable", + "esUi": "src/plugins/es_ui_shared", + "expressions_np": "src/plugins/expressions", + "expressions": "src/legacy/core_plugins/expressions", "inputControl": "src/legacy/core_plugins/input_control_vis", + "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", "interpreter": "src/legacy/core_plugins/interpreter", - "dashboardEmbeddableContainer": "src/legacy/core_plugins/dashboard_embeddable_container", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "embeddableApi": "src/plugins/embeddable", + "kbnESQuery": "packages/kbn-es-query", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", - "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", - "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", - "visTypeVega": "src/legacy/core_plugins/vis_type_vega", - "visTypeTable": "src/legacy/core_plugins/vis_type_table", + "kibana_react": "src/legacy/core_plugins/kibana_react", + "kibana-react": "src/plugins/kibana_react", + "navigation": "src/legacy/core_plugins/navigation", "regionMap": "src/legacy/core_plugins/region_map", + "server": "src/legacy/server", "statusPage": "src/legacy/core_plugins/status_page", + "telemetry": "src/legacy/core_plugins/telemetry", "tileMap": "src/legacy/core_plugins/tile_map", "timelion": "src/legacy/core_plugins/timelion", + "uiActions": "src/plugins/ui_actions", + "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", + "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", + "visTypeTable": "src/legacy/core_plugins/vis_type_table", "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": "src/legacy/core_plugins/vis_type_timeseries", - "kbnESQuery": "packages/kbn-es-query", - "inspector": "src/plugins/inspector", - "kibana-react": "src/plugins/kibana_react", - "telemetry": "src/legacy/core_plugins/telemetry", - "esUi": "src/plugins/es_ui_shared", - "uiActions": "src/plugins/ui_actions" + "visTypeVega": "src/legacy/core_plugins/vis_type_vega", + "visualizations": "src/plugins/visualizations" }, "exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"], "translations": [] diff --git a/Jenkinsfile b/Jenkinsfile index fca814b265295c..8d8579736f6392 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,7 @@ #!/bin/groovy library 'kibana-pipeline-library' +kibanaLibrary.load() stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a little bit timeout(time: 180, unit: 'MINUTES') { @@ -8,301 +9,44 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a ansiColor('xterm') { catchError { parallel([ - 'kibana-intake-agent': legacyJobRunner('kibana-intake'), - 'x-pack-intake-agent': legacyJobRunner('x-pack-intake'), - 'kibana-oss-agent': withWorkers('kibana-oss-tests', { buildOss() }, [ - 'oss-ciGroup1': getOssCiGroupWorker(1), - 'oss-ciGroup2': getOssCiGroupWorker(2), - 'oss-ciGroup3': getOssCiGroupWorker(3), - 'oss-ciGroup4': getOssCiGroupWorker(4), - 'oss-ciGroup5': getOssCiGroupWorker(5), - 'oss-ciGroup6': getOssCiGroupWorker(6), - 'oss-ciGroup7': getOssCiGroupWorker(7), - 'oss-ciGroup8': getOssCiGroupWorker(8), - 'oss-ciGroup9': getOssCiGroupWorker(9), - 'oss-ciGroup10': getOssCiGroupWorker(10), - 'oss-ciGroup11': getOssCiGroupWorker(11), - 'oss-ciGroup12': getOssCiGroupWorker(12), - 'oss-firefoxSmoke': getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), - // 'oss-visualRegression': getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), + 'kibana-intake-agent': kibanaPipeline.legacyJobRunner('kibana-intake'), + 'x-pack-intake-agent': kibanaPipeline.legacyJobRunner('x-pack-intake'), + 'kibana-oss-agent': kibanaPipeline.withWorkers('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ + 'oss-ciGroup1': kibanaPipeline.getOssCiGroupWorker(1), + 'oss-ciGroup2': kibanaPipeline.getOssCiGroupWorker(2), + 'oss-ciGroup3': kibanaPipeline.getOssCiGroupWorker(3), + 'oss-ciGroup4': kibanaPipeline.getOssCiGroupWorker(4), + 'oss-ciGroup5': kibanaPipeline.getOssCiGroupWorker(5), + 'oss-ciGroup6': kibanaPipeline.getOssCiGroupWorker(6), + 'oss-ciGroup7': kibanaPipeline.getOssCiGroupWorker(7), + 'oss-ciGroup8': kibanaPipeline.getOssCiGroupWorker(8), + 'oss-ciGroup9': kibanaPipeline.getOssCiGroupWorker(9), + 'oss-ciGroup10': kibanaPipeline.getOssCiGroupWorker(10), + 'oss-ciGroup11': kibanaPipeline.getOssCiGroupWorker(11), + 'oss-ciGroup12': kibanaPipeline.getOssCiGroupWorker(12), + 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), + 'oss-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld './test/scripts/jenkins_accessibility.sh' }), + 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), ]), - 'kibana-xpack-agent': withWorkers('kibana-xpack-tests', { buildXpack() }, [ - 'xpack-ciGroup1': getXpackCiGroupWorker(1), - 'xpack-ciGroup2': getXpackCiGroupWorker(2), - 'xpack-ciGroup3': getXpackCiGroupWorker(3), - 'xpack-ciGroup4': getXpackCiGroupWorker(4), - 'xpack-ciGroup5': getXpackCiGroupWorker(5), - 'xpack-ciGroup6': getXpackCiGroupWorker(6), - 'xpack-ciGroup7': getXpackCiGroupWorker(7), - 'xpack-ciGroup8': getXpackCiGroupWorker(8), - 'xpack-ciGroup9': getXpackCiGroupWorker(9), - 'xpack-ciGroup10': getXpackCiGroupWorker(10), - 'xpack-firefoxSmoke': getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), - // 'xpack-visualRegression': getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), + 'kibana-xpack-agent': kibanaPipeline.withWorkers('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), + 'xpack-ciGroup2': kibanaPipeline.getXpackCiGroupWorker(2), + 'xpack-ciGroup3': kibanaPipeline.getXpackCiGroupWorker(3), + 'xpack-ciGroup4': kibanaPipeline.getXpackCiGroupWorker(4), + 'xpack-ciGroup5': kibanaPipeline.getXpackCiGroupWorker(5), + 'xpack-ciGroup6': kibanaPipeline.getXpackCiGroupWorker(6), + 'xpack-ciGroup7': kibanaPipeline.getXpackCiGroupWorker(7), + 'xpack-ciGroup8': kibanaPipeline.getXpackCiGroupWorker(8), + 'xpack-ciGroup9': kibanaPipeline.getXpackCiGroupWorker(9), + 'xpack-ciGroup10': kibanaPipeline.getXpackCiGroupWorker(10), + 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), + 'xpack-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld './test/scripts/jenkins_xpack_accessibility.sh' }), + 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), ]), ]) } - node('flyweight') { - // If the build doesn't have a result set by this point, there haven't been any errors and it can be marked as a success - // The e-mail plugin for the infra e-mail depends upon this being set - currentBuild.result = currentBuild.result ?: 'SUCCESS' - - sendMail() - } + kibanaPipeline.sendMail() } } } } - -def withWorkers(name, preWorkerClosure = {}, workerClosures = [:]) { - return { - jobRunner('tests-xl', true) { - try { - doSetup() - preWorkerClosure() - - def nextWorker = 1 - def worker = { workerClosure -> - def workerNumber = nextWorker - nextWorker++ - - return { - workerClosure(workerNumber) - } - } - - def workers = [:] - workerClosures.each { workerName, workerClosure -> - workers[workerName] = worker(workerClosure) - } - - parallel(workers) - } finally { - catchError { - uploadAllGcsArtifacts(name) - } - - catchError { - runbldJunit() - } - - catchError { - publishJunit() - } - - catchError { - runErrorReporter() - } - } - } - } -} - -def getPostBuildWorker(name, closure) { - return { workerNumber -> - def kibanaPort = "61${workerNumber}1" - def esPort = "61${workerNumber}2" - def esTransportPort = "61${workerNumber}3" - - withEnv([ - "CI_WORKER_NUMBER=${workerNumber}", - "TEST_KIBANA_HOST=localhost", - "TEST_KIBANA_PORT=${kibanaPort}", - "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", - "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", - "TEST_ES_TRANSPORT_PORT=${esTransportPort}", - "IS_PIPELINE_JOB=1", - ]) { - closure() - } - } -} - -def getOssCiGroupWorker(ciGroup) { - return getPostBuildWorker("ciGroup" + ciGroup, { - withEnv([ - "CI_GROUP=${ciGroup}", - "JOB=kibana-ciGroup${ciGroup}", - ]) { - runbld "./test/scripts/jenkins_ci_group.sh" - } - }) -} - -def getXpackCiGroupWorker(ciGroup) { - return getPostBuildWorker("xpack-ciGroup" + ciGroup, { - withEnv([ - "CI_GROUP=${ciGroup}", - "JOB=xpack-kibana-ciGroup${ciGroup}", - ]) { - runbld "./test/scripts/jenkins_xpack_ci_group.sh" - } - }) -} - -def legacyJobRunner(name) { - return { - parallel([ - "${name}": { - withEnv([ - "JOB=${name}", - ]) { - jobRunner('linux && immutable', false) { - try { - runbld('.ci/run.sh', true) - } finally { - catchError { - uploadAllGcsArtifacts(name) - } - catchError { - publishJunit() - } - catchError { - runErrorReporter() - } - } - } - } - } - ]) - } -} - -def jobRunner(label, useRamDisk, closure) { - node(label) { - if (useRamDisk) { - // Move to a temporary workspace, so that we can symlink the real workspace into /dev/shm - def originalWorkspace = env.WORKSPACE - ws('/tmp/workspace') { - sh """ - mkdir -p /dev/shm/workspace - mkdir -p '${originalWorkspace}' # create all of the directories leading up to the workspace, if they don't exist - rm --preserve-root -rf '${originalWorkspace}' # then remove just the workspace, just in case there's stuff in it - ln -s /dev/shm/workspace '${originalWorkspace}' - """ - } - } - - def scmVars = checkout scm - - withEnv([ - "CI=true", - "HOME=${env.JENKINS_HOME}", - "PR_SOURCE_BRANCH=${env.ghprbSourceBranch ?: ''}", - "PR_TARGET_BRANCH=${env.ghprbTargetBranch ?: ''}", - "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", - "TEST_BROWSER_HEADLESS=1", - "GIT_BRANCH=${scmVars.GIT_BRANCH}", - ]) { - withCredentials([ - string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), - string(credentialsId: 'vault-role-id', variable: 'VAULT_ROLE_ID'), - string(credentialsId: 'vault-secret-id', variable: 'VAULT_SECRET_ID'), - ]) { - // scm is configured to check out to the ./kibana directory - dir('kibana') { - closure() - } - } - } - } -} - -// TODO what should happen if GCS, Junit, or email publishing fails? Unstable build? Failed build? - -def uploadGcsArtifact(workerName, pattern) { - def storageLocation = "gs://kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" // TODO - // def storageLocation = "gs://kibana-pipeline-testing/jobs/pipeline-test/${BUILD_NUMBER}/${workerName}" - - googleStorageUpload( - credentialsId: 'kibana-ci-gcs-plugin', - bucket: storageLocation, - pattern: pattern, - sharedPublicly: true, - showInline: true, - ) -} - -def uploadAllGcsArtifacts(workerName) { - def ARTIFACT_PATTERNS = [ - 'target/kibana-*', - 'target/junit/**/*', - 'test/**/screenshots/**/*.png', - 'test/functional/failure_debug/html/*.html', - 'x-pack/test/**/screenshots/**/*.png', - 'x-pack/test/functional/failure_debug/html/*.html', - 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', - ] - - ARTIFACT_PATTERNS.each { pattern -> - uploadGcsArtifact(workerName, pattern) - } -} - -def publishJunit() { - junit(testResults: 'target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) -} - -def sendMail() { - sendInfraMail() - sendKibanaMail() -} - -def sendInfraMail() { - catchError { - step([ - $class: 'Mailer', - notifyEveryUnstableBuild: true, - recipients: 'infra-root+build@elastic.co', - sendToIndividuals: false - ]) - } -} - -def sendKibanaMail() { - catchError { - def buildStatus = buildUtils.getBuildStatus() - - if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { - emailext( - to: 'build-kibana@elastic.co', - subject: "${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - ${buildStatus}", - body: '${SCRIPT,template="groovy-html.template"}', - mimeType: 'text/html', - ) - } - } -} - -def runbld(script, enableJunitProcessing = false) { - def extraConfig = enableJunitProcessing ? "" : "--config ${env.WORKSPACE}/kibana/.ci/runbld_no_junit.yml" - - sh "/usr/local/bin/runbld -d '${pwd()}' ${extraConfig} ${script}" -} - -def runbldJunit() { - sh "/usr/local/bin/runbld -d '${pwd()}' ${env.WORKSPACE}/kibana/test/scripts/jenkins_runbld_junit.sh" -} - -def bash(script) { - sh "#!/bin/bash\n${script}" -} - -def doSetup() { - runbld "./test/scripts/jenkins_setup.sh" -} - -def buildOss() { - runbld "./test/scripts/jenkins_build_kibana.sh" -} - -def buildXpack() { - runbld "./test/scripts/jenkins_xpack_build_kibana.sh" -} - -def runErrorReporter() { - bash """ - source src/dev/ci_setup/setup_env.sh - node scripts/report_failed_tests - """ -} diff --git a/README.md b/README.md index 2d33cd218b3dac..03ce6a6525873c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ _Note: The version numbers below are only examples, meant to illustrate the rela ## Questions? Problems? Suggestions? -- If you've found a bug or want to request a feature, please create a [GitHub Issue](https://github.com/elastic/kibana/issues/new). -Please check to make sure someone else hasn't already created an issue for the same topic. +- If you've found a bug or want to request a feature, please create a [GitHub Issue](https://github.com/elastic/kibana/issues/new/choose). + Please check to make sure someone else hasn't already created an issue for the same topic. - Need help using Kibana? Ask away on our [Kibana Discuss Forum](https://discuss.elastic.co/c/kibana) and a fellow community member or Elastic engineer will be glad to help you out. diff --git a/config/kibana.yml b/config/kibana.yml index 9525a6423d90a0..47482f9e6d59f8 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -50,7 +50,8 @@ #server.ssl.key: /path/to/your/server.key # Optional settings that provide the paths to the PEM-format SSL certificate and key files. -# These files validate that your Elasticsearch backend uses the same key files. +# These files are used to verify the identity of Kibana to Elasticsearch and are required when +# xpack.ssl.verification_mode in Elasticsearch is set to either certificate or full. #elasticsearch.ssl.certificate: /path/to/your/client.crt #elasticsearch.ssl.key: /path/to/your/client.key diff --git a/docs/canvas/canvas-expressions.asciidoc b/docs/canvas/canvas-expressions.asciidoc deleted file mode 100644 index 0fcc69c6db726e..00000000000000 --- a/docs/canvas/canvas-expressions.asciidoc +++ /dev/null @@ -1,39 +0,0 @@ -[[canvas-expression-editor]] -=== Customize your element with the expression editor - -Each element is backed by an expression that represents the element style and data. To further define the appearance and behavior of the element, use the expression editor. - -. In the lower right corner, click *Expression editor*. - -. Edit the style and data parts of the expression that you want to change. - -. Click *Run*. - -. If you like what you see, click *Close*. - -For information about the Canvas expression language, see <>. - -//Insert expression video. - -[float] -[[canvas-expression-editor-example]] -=== Example: Using the expression editor - -Build a complex element using expressions. - -``` -image mode="contain" dataurl={ -asset { -filters | essql -query="SELECT host,response -FROM kibana_sample_data_logs -WHERE host='artifacts.elastic.co' -ORDER BY timestamp DESC -LIMIT 1"| -alterColumn "response" type="number" | -getCell "response" | -if {compare lt to=400} then="asset-0a807073-d056-4c7b-9bf4-225b71e47243" else="asset-1343672d-7c02-4402-929e-0f8fef69cddd" -} -} | render - -``` \ No newline at end of file diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index df4b17e9057721..07f3cf028dc0e3 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -25,6 +25,32 @@ A † denotes an argument can be passed multiple times. Returns `true` if all of the conditions are met. See also <>. +*Expression syntax* +[source,js] +---- +all {neq “foo”} {neq “bar”} {neq “fizz”} +all condition={gt 10} condition={lt 20} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| math "mean(percent_uptime)" +| formatnumber "0.0%" +| metric "Average uptime" + metricFont={ + font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" + color={ + if {all {gte 0} {lt 0.8}} then="red" else="green" + } + align="center" lHeight=48 + } +| render +---- +This sets the color of the metric text to `”red”` if the context passed into `metric` is greater than or equal to 0 and less than 0.8. Otherwise, the color is set to `"green"`. + *Accepts:* `null` [cols="3*^<"] @@ -47,6 +73,24 @@ Alias: `condition` Converts between core types, including `string`, `number`, `null`, `boolean`, and `date`, and renames columns. See also <> and <>. +*Expression syntax* +[source,js] +---- +alterColumn “cost” type=”string” +alterColumn column=”@timestamp” name=”foo” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| alterColumn “time” name=”time_in_ms” type=”number” +| table +| render +---- +This renames the `time` column to `time_in_ms` and converts the type of the column’s values from `date` to `number`. + *Accepts:* `datatable` [cols="3*^<"] @@ -77,6 +121,27 @@ Alias: `column` Returns `true` if at least one of the conditions is met. See also <>. +*Expression syntax* +[source,js] +---- +any {eq “foo”} {eq “bar”} {eq “fizz”} +any condition={lte 10} condition={gt 30} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| filterrows { + getCell "project" | any {eq "elasticsearch"} {eq "kibana"} {eq "x-pack"} + } +| pointseries color="project" size="max(price)" +| pie +| render +---- +This filters out any rows that don’t contain `“elasticsearch”`, `“kibana”` or `“x-pack”` in the `project` field. + *Accepts:* `null` [cols="3*^<"] @@ -99,6 +164,28 @@ Alias: `condition` Creates a `datatable` with a single value. See also <>. +*Expression syntax* +[source,js] +---- +as +as “foo” +as name=”bar” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| ply by="project" fn={math "count(username)" | as "num_users"} fn={math "mean(price)" | as "price"} +| pointseries x="project" y="num_users" size="price" color="project" +| plot +| render +---- +`as` casts any primitive value (`string`, `number`, `date`, `null`) into a `datatable` with a single row and a single column with the given name (or defaults to `"value"` if no name is provided). This is useful when piping a primitive value into a function that only takes `datatable` as an input. + +In the example above, `ply` expects each `fn` subexpression to return a `datatable` in order to merge the results of each `fn` back into a `datatable`, but using a `math` aggregation in the subexpressions returns a single `math` value, which is then cast into a `datatable` using `as`. + *Accepts:* `string`, `boolean`, `number`, `null` [cols="3*^<"] @@ -123,6 +210,21 @@ Default: `"value"` Retrieves Canvas workpad asset objects to provide as argument values. Usually images. +*Expression syntax* +[source,js] +---- +asset "asset-52f14f2b-fee6-4072-92e8-cd2642665d02" +asset id="asset-498f7429-4d56-42a2-a7e4-8bf08d98d114" +---- + +*Code example* +[source,text] +---- +image dataurl={asset "asset-c661a7cc-11be-45a1-a401-d7592ea7917a"} mode="contain" +| render +---- +The image asset stored with the ID `“asset-c661a7cc-11be-45a1-a401-d7592ea7917a”` is passed into the `dataurl` argument of the `image` function to display the stored asset. + *Accepts:* `null` [cols="3*^<"] @@ -145,6 +247,27 @@ Alias: `id` Configures the axis of a visualization. Only used with <>. +*Expression syntax* +[source,js] +---- +axisConfig show=false +axisConfig position=”right” min=0 max=10 tickSize=1 +---- + +*Code example* +[source,text] +---- +filters +| demodata +| pointseries x="size(cost)" y="project" color="project" +| plot defaultStyle={seriesStyle bars=0.75 horizontalBars=true} + legend=false + xaxis={axisConfig position="top" min=0 max=400 tickSize=100} + yaxis={axisConfig position="right"} +| render +---- +This sets the `x-axis` to display on the top of the chart and sets the range of values to `0-400` with ticks displayed at `100` intervals. The `y-axis` is configured to display on the `right`. + *Accepts:* `null` [cols="3*^<"] @@ -188,6 +311,34 @@ Default: `true` Builds a `case` (including a condition/result) to pass to the <> function. +*Expression syntax* +[source,js] +---- +case 0 then=”red” +case when=5 then=”yellow” +case if={lte 50} then=”green” +---- + +*Code example* +[source,text] +---- +math "random()" +| progress shape="gauge" label={formatnumber "0%"} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" align="center" + color={ + switch {case if={lte 0.5} then="green"} + {case if={all {gt 0.5} {lte 0.75}} then="orange"} + default="red" + }} + valueColor={ + switch {case if={lte 0.5} then="green"} + {case if={all {gt 0.5} {lte 0.75}} then="orange"} + default="red" + } +| render +---- +This sets the color of the progress indicator and the color of the label to `"green"` if the value is less than or equal to `0.5`, `"orange"` if the value is greater than `0.5` and less than or equal to `0.75`, and `"red"` if `none` of the case conditions are met. + *Accepts:* `any` [cols="3*^<"] @@ -229,6 +380,24 @@ Clears the _context_, and returns `null`. Includes or excludes columns from a data table. If you specify both, this will exclude first. +*Expression syntax* +[source,js] +---- +columns include=”@timestamp, projects, cost” +columns exclude=”username, country, age” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| columns include="price, cost, state, project" +| table +| render +---- +This only keeps the `price`, `cost`, `state`, and `project` columns from the `demodata` data source and removes all other columns. + *Accepts:* `datatable` [cols="3*^<"] @@ -257,6 +426,31 @@ Compares the _context_ to specified value to determine `true` or `false`. Usually used in combination with <> or <>. This only works with primitive types, such as `number`, `string`, and `boolean`. See also <>, <>, <>, <>, <>, and <>. +*Expression syntax* +[source,js] +---- +compare “neq” to=”elasticsearch” +compare op=”lte” to=100 +---- + +*Code example* +[source,text] +---- +filters +| demodata +| mapColumn project + fn=${getCell project | + switch + {case if={compare eq to=kibana} then=kibana} + {case if={compare eq to=elasticsearch} then=elasticsearch} + default="other" + } +| pointseries size="size(cost)" color="project" +| pie +| render +---- +This maps all `project` values that aren’t `“kibana”` and `“elasticsearch”` to `“other”`. Alternatively, you can use the individual comparator functions instead of compare. See <>, <>, <>, <>, <>, and <>. + *Accepts:* `string`, `number`, `boolean`, `null` [cols="3*^<"] @@ -287,6 +481,35 @@ Alias: `this`, `b` Creates an object used for styling an element's container, including background, border, and opacity. +*Expression syntax* +[source,js] +---- +containerStyle backgroundColor=”red”’ +containerStyle borderRadius=”50px” +containerStyle border=”1px solid black” +containerStyle padding=”5px” +containerStyle opacity=”0.5” +containerStyle overflow=”hidden” +containerStyle backgroundImage={asset id=asset-f40d2292-cf9e-4f2c-8c6f-a504a25e949c} + backgroundRepeat="no-repeat" + backgroundSize="cover" +---- + +*Code example* +[source,text] +---- +shape "star" fill="#E61D35" maintainAspect=true +| render + containerStyle={ + containerStyle backgroundColor="#F8D546" + borderRadius="200px" + border="4px solid #05509F" + padding="0px" + opacity="0.9" + overflow="hidden" + } +---- + *Accepts:* `null` [cols="3*^<"] @@ -346,6 +569,22 @@ Default: `"hidden"` Returns whatever you pass into it. This can be useful when you need to use the _context_ as an argument to a function as a sub-expression. +*Expression syntax* +[source,js] +---- +context +---- + +*Code example* +[source,text] +---- +date +| formatdate "LLLL" +| markdown "Last updated: " {context} +| render +---- +Using the `context` function allows us to pass the output, or _context_, of the previous function as a value to an argument in the next function. Here we get the formatted date string from the previous function and pass it as `content` for the markdown element. + *Accepts:* `any` *Returns:* Original _context_ @@ -356,6 +595,26 @@ _context_ as an argument to a function as a sub-expression. Creates a `datatable` from CSV input. +*Expression syntax* +[source,js] +---- +csv “fruit, stock + kiwi, 10 + Banana, 5” +---- + +*Code example* +[source,text] +---- +csv "fruit,stock + kiwi,10 + banana,5" +| pointseries color=fruit size=stock +| pie +| render +---- +This is useful for quickly mocking data. + *Accepts:* `null` [cols="3*^<"] @@ -390,6 +649,30 @@ Alias: `data` Returns the current time, or a time parsed from a `string`, as milliseconds since epoch. +*Expression syntax* +[source,js] +---- +date +date value=1558735195 +date “2019-05-24T21:59:55+0000” +date “01/31/2019” format=”MM/DD/YYYY” +---- + +*Code example* +[source,text] +---- +date +| formatdate "LLL" +| markdown {context} + font={font family="Arial, sans-serif" size=30 align="left" + color="#000000" + weight="normal" + underline=false + italic=false} +| render +---- +Using `date` without passing any arguments will return the current date and time. + *Accepts:* `null` [cols="3*^<"] @@ -418,6 +701,24 @@ using the `format` argument. Must be an ISO8601 string or you must provide the f A mock data set that includes project CI times with usernames, countries, and run phases. +*Expression syntax* +[source,js] +---- +demodata +demodata “ci” +demodata type=”shirts” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| table +| render +---- +`demodata` is a mock data set that you can use to start playing around in Canvas. + *Accepts:* `filter` [cols="3*^<"] @@ -442,6 +743,23 @@ Default: `"ci"` Executes multiple sub-expressions, then returns the original _context_. Use for running functions that produce an action or side effect without changing the original _context_. +*Expression syntax* +[source,js] +---- +do fn={something cool} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| do fn={something cool} +| table +| render +---- +`do` should be used to invoke a function that produces as a side effect without changing the `context`. + *Accepts:* `any` [cols="3*^<"] @@ -464,6 +782,22 @@ Aliases: `expression`, `exp`, `fn`, `function` Configures a dropdown filter control element. +*Expression syntax* +[source,js] +---- +dropdownControl valueColumn=project filterColumn=project +dropdownControl valueColumn=agent filterColumn=agent.keyword filterGroup=group1 +---- + +*Code example* +[source,text] +---- +demodata +| dropdownControl valueColumn=project filterColumn=project +| render +---- +This creates a dropdown filter element. It requires a data source and uses the unique values from the given `valueColumn` (i.e. `project`) and applies the filter to the `project` column. Note: `filterColumn` should point to a keyword type field for Elasticsearch data sources. + *Accepts:* `datatable` [cols="3*^<"] @@ -496,6 +830,33 @@ Configures a dropdown filter control element. Returns whether the _context_ is equal to the argument. +*Expression syntax* +[source,js] +---- +eq true +eq null +eq 10 +eq “foo” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| mapColumn project + fn=${getCell project | + switch + {case if={eq kibana} then=kibana} + {case if={eq elasticsearch} then=elasticsearch} + default="other" + } +| pointseries size="size(cost)" color="project" +| pie +| render +---- +This changes all values in the project column that don’t equal `“kibana”` or `“elasticsearch”` to `“other”`. + *Accepts:* `boolean`, `number`, `string`, `null` [cols="3*^<"] @@ -518,6 +879,28 @@ Alias: `value` Queries {es} for the number of hits matching the specified query. +*Expression syntax* +[source,js] +---- +escount index=”logstash-*” +escount "currency:\"EUR\"" index=”kibana_sample_data_ecommerce” +escount query="response:404" index=”kibana_sample_data_logs” +---- + +*Code example* +[source,text] +---- +filters +| escount "Cancelled:true" index="kibana_sample_data_flights" +| math "value" +| progress shape="semicircle" + label={formatnumber 0,0} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center} + max={filters | escount index="kibana_sample_data_flights"} +| render +---- +The first `escount` expression retrieves the number of flights that were cancelled. The second `escount` expression retrieves the total number of flights. + *Accepts:* `filter` [cols="3*^<"] @@ -549,6 +932,34 @@ Default: `_all` Queries {es} for raw documents. Specify the fields you want to retrieve, especially if you are asking for a lot of rows. +*Expression syntax* +[source,js] +---- +esdocs index=”logstash-*” +esdocs "currency:\"EUR\"" index=”kibana_sample_data_ecommerce” +esdocs query="response:404" index=”kibana_sample_data_logs” +esdocs index=”kibana_sample_data_flights” count=100 +esdocs index="kibana_sample_data_flights" sort="AvgTicketPrice, asc" +---- + +*Code example* +[source,text] +---- +filters +| esdocs index="kibana_sample_data_ecommerce" + fields="customer_gender, taxful_total_price, order_date" + sort="order_date, asc" + count=10000 +| mapColumn "order_date" + fn={getCell "order_date" | date {context} | rounddate "YYYY-MM-DD"} +| alterColumn "order_date" type="date" +| pointseries x="order_date" y="sum(taxful_total_price)" color="customer_gender" +| plot defaultStyle={seriesStyle lines=3} + palette={palette "#7ECAE3" "#003A4D" gradient=true} +| render +---- +This retrieves the latest 10000 documents data from the `kibana_sample_data_ecommerce` index sorted by `order_date` in ascending order and only requests the `customer_gender`, `taxful_total_price`, and `order_date` fields. + *Accepts:* `filter` [cols="3*^<"] @@ -593,6 +1004,21 @@ Default: `"_all"` Queries {es} using {es} SQL. +*Expression syntax* +[source,js] +---- +essql query=”SELECT * FROM \”logstash*\”” +essql “SELECT * FROM \”apm*\”” count=10000 +---- + +*Code example* +[source,text] +---- +filters +| essql query="SELECT Carrier, FlightDelayMin, AvgTicketPrice FROM \"kibana_sample_data_flights\"" +---- +This retrieves the `Carrier`, `FlightDelayMin`, and `AvgTicketPrice` fields from the “kibana_sample_data_flights” index. + *Accepts:* `filter` [cols="3*^<"] @@ -627,6 +1053,26 @@ Default: `UTC` Creates a filter that matches a given column to an exact value. +*Expression syntax* +[source,js] +---- +exactly “state” value=”running” +exactly “age” value=50 filterGroup=”group2” +exactly column=“project” value=”beats” +---- + +*Code example* +[source,text] +---- +filters +| exactly column=project value=elasticsearch +| demodata +| pointseries x=project y="mean(age)" +| plot defaultStyle={seriesStyle bars=1} +| render +---- +The `exactly` filter here is added to existing filters retrieved by the `filters` function and further filters down the data to only have `”elasticsearch”` data. The `exactly` filter only applies to this one specific element and will not affect other elements in the workpad. + *Accepts:* `filter` [cols="3*^<"] @@ -664,6 +1110,29 @@ capitalization. Filters rows in a `datatable` based on the return value of a sub-expression. +*Expression syntax* +[source,js] +---- +filterrows {getCell “project” | eq “kibana”} +filterrows fn={getCell “age” | gt 50} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| filterrows {getCell "country" | any {eq "IN"} {eq "US"} {eq "CN"}} +| mapColumn "@timestamp" + fn={getCell "@timestamp" | rounddate "YYYY-MM"} +| alterColumn "@timestamp" type="date" +| pointseries x="@timestamp" y="mean(cost)" color="country" +| plot defaultStyle={seriesStyle points="2" lines="1"} + palette={palette "#01A4A4" "#CC6666" "#D0D102" "#616161" "#00A1CB" "#32742C" "#F18D05" "#113F8C" "#61AE24" "#D70060" gradient=false} +| render +---- +This uses `filterrows` to only keep data from India (`IN`), the United States (`US`), and China (`CN`). + *Accepts:* `datatable` [cols="3*^<"] @@ -688,6 +1157,34 @@ and a `false` value removes it. Aggregates element filters from the workpad for use elsewhere, usually a data source. +*Expression syntax* +[source,js] +---- +filters +filters group=”timefilter1” +filters group=”timefilter2” group=”dropdownfilter1” ungrouped=true +---- + +*Code example* +[source,text] +---- +filters group=group2 ungrouped=true +| demodata +| pointseries x="project" y="size(cost)" color="project" +| plot defaultStyle={seriesStyle bars=0.75} legend=false + font={ + font size=14 + family="'Open Sans', Helvetica, Arial, sans-serif" + align="left" + color="#FFFFFF" + weight="lighter" + underline=true + italic=true + } +| render +---- +`filters` sets the existing filters as context and accepts `group` parameter to create filter groups. + *Accepts:* `null` [cols="3*^<"] @@ -718,6 +1215,38 @@ Default: `false` Creates a font style. +*Expression syntax* +[source,js] +---- +font size=12 +font family=Arial +font align=middle +font color=pink +font weight=lighter +font underline=true +font italic=false +font lHeight=32 +---- + +*Code example* +[source,text] +---- +filters +| demodata +| pointseries x="project" y="size(cost)" color="project" +| plot defaultStyle={seriesStyle bars=0.75} legend=false + font={ + font size=14 + family="'Open Sans', Helvetica, Arial, sans-serif" + align="left" + color="#FFFFFF" + weight="lighter" + underline=true + italic=true + } +| render +---- + *Accepts:* `null` [cols="3*^<"] @@ -781,6 +1310,25 @@ Default: `"normal"` Formats an ISO8601 date string or a date in milliseconds since epoch using MomentJS. See https://momentjs.com/docs/#/displaying/. +*Expression syntax* +[source,js] +---- +formatdate format=”YYYY-MM-DD” +formatdate “MM/DD/YYYY” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| mapColumn "time" fn={getCell time | formatdate "MMM 'YY"} +| pointseries x="time" y="sum(price)" color="state" +| plot defaultStyle={seriesStyle points=5} +| render +---- +This transforms the dates in the `time` field into strings that look like `“Jan ‘19”`, `“Feb ‘19”`, etc. using a MomentJS format. + *Accepts:* `number`, `string` [cols="3*^<"] @@ -803,6 +1351,26 @@ Alias: `format` Formats a `number` into a formatted `string` using NumeralJS. See http://numeraljs.com/#format. +*Expression syntax* +[source,js] +---- +formatnumber format=”$0,0.00” +formatnumber “0.0a” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| math "mean(percent_uptime)" +| progress shape="gauge" + label={formatnumber "0%"} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align="center"} +| render +---- +The `formatnumber` subexpression receives the same `context` as the `progress` function, which is the output of the `math` function. It formats the value into a percentage. + *Accepts:* `number` [cols="3*^<"] diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 5cc5b953e5775f..119ede16fe1e7b 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -2,47 +2,100 @@ [[workpad-share-options]] == Share your workpad -When you are ready to share your workpad, create a PDF, or export your workpad. +When you've finished your workpad, you can share it outside of {kib}. + +[float] +[[export-single-workpad]] +=== Export workpads + +Create a JSON file of your workpad that you can export outside of {kib}. + +. From your workpad, click the *Share workpad* icon in the upper left corner. + +. Select *Download as JSON*. ++ +[role="screenshot"] +image::images/canvas-export-workpad.png[Export single workpad] + +Want to export multiple workpads? Go to the *Canvas workpads* view, select the workpads you want to export, then click *Export*. [float] [[create-workpad-pdf]] === Create a PDF -To view your workpad outside of Kibana, generate a PDF. +Create a PDF copy of your workpad that you can save and share outside of {kib}. . If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. -. Click *Generate PDF*. +. Click *Generate PDF*. + [role="screenshot"] image::images/canvas-generate-pdf.gif[Generate PDF] [float] -[[export-workpad]] -=== Export your workpad +[[create-workpad-URL]] +=== Create a POST URL + +Create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. + +For more information, refer to <>. + +. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. + +. From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. -To share your workpad with another author, export it as a JSON file. +. Click *Copy POST URL*. ++ +[role="screenshot"] +image::images/canvas-create-URL.gif[Create POST URL] [float] -[[export-single-workpad]] -==== Export a single workpad +[[add-workpad-website]] +=== Share the workpad on a website -. From your workpad, click the *Share workpad* icon in the upper left corner. +beta[] Download the workpad and share it on any website, then customize the workpad behavior to autoplay the pages or hide the toolbar. -. Select *Download as JSON*. +. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. + +. From your workpad, click the *Share this workpad* icon in the upper left corner, then select *Share on a website*. + +. On the *Share on a website* pane, follow the instructions. + +. To customize the workpad behavior to autoplay the pages or hide the toolbar, use the inline parameters. ++ +To make sure that your data remains secure, the data in the JSON file is not connected to {kib}. Canvas does not display elements that manipulate the data on the workpad. + [role="screenshot"] -image::images/canvas-export-workpad.png[Export single workpad] +image::images/canvas-embed_workpad.gif[Share the workpad on a website] ++ +NOTE: Shareable workpads encode the current state of the workpad in a JSON file. When you make changes to the workpad, the changes do not appear in the shareable workpad on your website. [float] -[[export-multiple-workpads]] -==== Export multiple workpads +[[change-the-workpad-settings]] +=== Change the shareable workpad settings -. Go to the *Canvas workpads* page. +After you've added the workpad to your website, you can change the autoplay and toolbar settings. -. Select the workpads you want to export +[float] +[[shareable-workpad-enable-autoplay]] +==== Change the autoplay settings -. Click *Export*. +. In the lower right corner of the shareable workpad, click the settings icon. +. Click *Auto Play*, then change the settings. ++ +[role="screenshot"] +image::images/canvas_share_autoplay_480.gif[Autoplay settings] + +[float] +[[hide-workpad-toolbar]] +==== Change the toolbar settings + +. In the lower right corner, click the settings icon. + +. Click *Toolbar*, then change the settings. ++ +[role="screenshot"] +image::images/canvas_share_hidetoolbar_480.gif[Hide toolbar settings] diff --git a/docs/canvas/canvas-workpad.asciidoc b/docs/canvas/canvas-workpad.asciidoc index f664f055a5ff65..f833bd903b0bca 100644 --- a/docs/canvas/canvas-workpad.asciidoc +++ b/docs/canvas/canvas-workpad.asciidoc @@ -18,17 +18,17 @@ To create a workpad, you can: [[blank-canvas-workpad]] === Start with a blank page -To use the background colors, images, and data of your choice, start with a blank workpad. +To use the background colors, images, and data of your choice, start with a blank workpad. . Open *Canvas*. -. On the *Canvas workpads* page, click *Create workpad*. +. On the *Canvas workpads* view, click *Create workpad*. . Add a *Name* to your workpad. -. In the *Width* and *Height* fields, specify the size. +. In the *Width* and *Height* fields, specify the size. -. Select the layout. +. Select the layout. + For example, click *720p* for a traditional presentation layout. @@ -45,7 +45,7 @@ If you're unsure about where to start, you can use one of the preconfigured temp . Open *Canvas*. -. On the *Canvas workpads* page, select *Templates*. +. On the *Canvas workpads* view, select *Templates*. . Click the preconfigured template that you want to use. @@ -59,7 +59,7 @@ When you want to use a workpad that someone else has already started, import the . Open *Canvas*. -. On the *Canvas workpads* page, click and drag the file to the *Import workpad JSON file* field. +. On the *Canvas workpads* view, click and drag the file to the *Import workpad JSON file* field. [float] [[sample-data-workpad]] @@ -67,11 +67,11 @@ When you want to use a workpad that someone else has already started, import the Each of the sample data sets comes with a Canvas workpad that you can use for your own workpad inspiration. -. Add a {kibana-ref}/add-sample-data.html[sample data set]. +. Add a {kibana-ref}/add-sample-data.html[sample data set]. . On the *Add Data to Kibana* page, click the *View data* dropdown list, then select *Canvas*. + -Need some more workpad inspiration? Check out the link:https://www.elastic.co/blog/[Elastic Blog]. +Need some more workpad inspiration? Check out the link:https://www.elastic.co/blog/[Elastic Blog]. [float] [[apply-workpad-styles]] diff --git a/docs/code/code-basic-nav.asciidoc b/docs/code/code-basic-nav.asciidoc deleted file mode 100644 index ccd290532b6698..00000000000000 --- a/docs/code/code-basic-nav.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[[code-basic-nav]] -== Basic navigation - -[float] -==== View file structure and information -The *File* tree on the left is the primary way to navigate through your folder structure. When you are in a directory, *Code* also presents a finder-like view on the right. Additionally, a file path breadcrumb makes it easy to go back to any parent directory. - -[float] -==== Git History and Blame -The *Directory* view shows the most recent commits. Clicking *View More* or *History* shows the complete commit history of the current folder or file. - -[role="screenshot"] -image::images/code-history.png[] - -Clicking *Blame* shows the most recent commit per line. - -[role="screenshot"] -image::images/code-blame.png[] - -[float] -==== Branch selector -You can use the Branch selector to view different branches of a repo. Note that code intelligence and search index are not available for any branch other than the master branch. - - -include::code-semantic-nav.asciidoc[] diff --git a/docs/code/code-import-first-repo.asciidoc b/docs/code/code-import-first-repo.asciidoc deleted file mode 100644 index 0c8b2660edcab1..00000000000000 --- a/docs/code/code-import-first-repo.asciidoc +++ /dev/null @@ -1,48 +0,0 @@ -[[code-import-first-repo]] -== Import your first repo - -The easiest way to get started with *Code* is to import a real-world repository. - -[float] -==== Before you begin -You must have a {kib} instance up and running. - -If you are in an environment where you have multiple {kib} instances in a cluster, see the <>. - -[float] -==== Enable Code app -While in beta, you can turn on *Code* by adding the following line to `kibana.yaml`: - -[source,yaml] ----- -xpack.code.ui.enabled: true ----- - -[float] -==== Import your first repository -. In {Kib}, navigate to *Code*. - -. In the *Repository URL* field, paste the following GitHub clone URL: -+ -[source,bash] ----- -https://github.com/Microsoft/TypeScript-Node-Starter ----- - -`https` is recommend for cloning most git repositories. To clone a private repository, <>. - -. Click *Import*. -+ -A new item in the list displays the cloning and indexing progress of the `TypeScript-Node-Starter` repo. -+ -[role="screenshot"] -image::images/code-import-repo.png[] - -. After the indexing is complete, navigate to the repo by clicking its name in the list. -+ -[role="screenshot"] -image::images/code-starter-root.png[] -+ -Congratulations! You just imported your first repo into *Code*. - -include::code-repo-management.asciidoc[] diff --git a/docs/code/code-install-lang-server.asciidoc b/docs/code/code-install-lang-server.asciidoc deleted file mode 100644 index 4d4349e654c080..00000000000000 --- a/docs/code/code-install-lang-server.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[[code-install-lang-server]] -== Install language server - -*Code* comes with built-in language support for TypeScript. You can install additional languages as a {kib} plugin. Plugin's reduce the distribution size to run Code inside {kib}. Install only the languages needed for your indexed repositories. - -[role="screenshot"] -image::images/code-lang-server-tab.png[] - -For the current version, *Code* supports the following languages in addition to TypeScript: - -* `Java` -* `GO` - -You can check the status of the language servers and get installation instructions on the *Language Servers* tab. Make sure the status of the language server is `INSTALLED` or `RUNNING` after you restart the {kib} instance. -[role="screenshot"] -image::images/code-lang-server-status.png[] - -//// -[float] -=== Ctag language server -*Code* also uses a Ctag language server to generate symbol information and code intelligence when a dedicated language server is not available. The code intelligence information generated by the Ctag language server is less accurate but covers more languages. -//// - - -include::code-basic-nav.asciidoc[] diff --git a/docs/code/code-multiple-kibana-instances-config.asciidoc b/docs/code/code-multiple-kibana-instances-config.asciidoc deleted file mode 100644 index 359e0764d28f1e..00000000000000 --- a/docs/code/code-multiple-kibana-instances-config.asciidoc +++ /dev/null @@ -1,10 +0,0 @@ -[[code-multiple-kibana-instances-config]] -== Config for multiple {kib} instances -If you are using multiple instances of {kib}, you must manually assign at least one {kib} instance as a *Code* `node`. Add the following line of code to your `kibana.yml` file for each {kib} instance you wish to use and restart the instances: - -[source,yaml] ----- -xpack.code.codeNodeUrl: 'http://$YourCodeNodeAddress' ----- - -`$YourCodeNoteAddress` is the URL of your assigned *Code* node accessible by other {kib} instances. diff --git a/docs/code/code-repo-management.asciidoc b/docs/code/code-repo-management.asciidoc deleted file mode 100644 index 1fbd8934f57bef..00000000000000 --- a/docs/code/code-repo-management.asciidoc +++ /dev/null @@ -1,76 +0,0 @@ -[[code-repo-management]] -== Repo management - -Code starts with an overview of your repositories. You can then use the UI to add, delete, and reindex a repo. -[role="screenshot"] -image::images/code-repo-management.png[] - -[float] -[[add-delete-a-repo]] -==== Add and delete a repo -The <> page provides step-by-step instructions for adding a GitHub repo to *Code*. You can fine-tune the hostname of the git clone URL in your `kibana.yml` file. - -For security reasons, Code allows only a few <>, such as github.com. To clone private repositories see <>. - -To delete a repository, go to the **Repositories** tab, find the name of the repo, and click *Delete*. - -[float] -[[clone-private-repo]] -==== Clone private repo with an SSH key -Clones of private repos require an SSH key for authentication. The username associated with your host must have write access to the repository you want to clone. - -The following section provides links for generating your ssh key through GitHub. If you already have an SSH key added through your host, skip to step 4. - -1. https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key[Generate an ssh key]. - -2. https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#adding-your-ssh-key-to-the-ssh-agent[Add the ssh key to your ssh-agent.] - -3. https://help.github.com/en/articles/adding-a-new-ssh-key-to-your-github-account[Add the ssh key to your host]. - -4. Copy the ssh key into `data/code/credentials/` under the {kib} folder. - -You can now copy private Git repositories into Code. - -To delete a repository, find the go to the **Repositories** tab, find the name of the repo and click *Delete*. - -[float] -[[reindex-a-repo]] -==== Reindex a repo -*Code* automatically reindexes an imported repo at set intervals, but in some cases, you might need to refresh the index manually. For example, you might refresh an index after a new language server installs. Or, you might want to update the index to the HEAD revision immediately. Click *Reindex* to initiate a reindex. - -[float] -[[clone-url-management]] -==== Clone URL management -For security reasons, *Code* only allows the following hostnames in the git clone URL by default: - -- `github.com` -- `gitlab.com` -- `bitbucket.org` -- `gitbox.apache.org` -- `eclipse.org` - -You can add your own hostname (for example, acme.com) to the whitelist by adding the following line to your `config/kibana.yaml` file: - -[source,yaml] ----- -xpack.code.security.gitHostWhitelist: [ "github.com", "gitlab.com", "bitbucket.org", "gitbox.apache.org", "eclipse.org", "acme.com" ] ----- - -Set `xpack.code.security.gitHostWhitelist` to [] (empty list) allow any hostname. - -You can also control the protocol to use for the clone address. By default, the following protocols are supported: `[ 'https', 'git', 'ssh' ]`. You can change this value by adding the following line to your `config/kibana.yaml` file. In this example, the user only wants to support the `https` protocol: - -[source,yaml] ----- -xpack.code.security.gitProtocolWhitelist: [ "https" ] ----- - -[float] -[[repo-limitations]] -==== Limitations -Consider the following limitations before cloning repositories: - -- Only Git is supported. Other version control systems, such as Mercurial and SVN, are not supported. -- Your disk might not have enough space to clone repositories due to {kib} watermark settings. To update your watermark settings, contact your system administrator and request additional disk space. - -include::code-install-lang-server.asciidoc[] diff --git a/docs/code/code-search.asciidoc b/docs/code/code-search.asciidoc deleted file mode 100644 index c3086bfb850a0f..00000000000000 --- a/docs/code/code-search.asciidoc +++ /dev/null @@ -1,24 +0,0 @@ -[[code-search]] -== Search - -[float] -==== Typeahead search -The search bar is designed to help you find what you're looking for as quickly as possible. It shows `Symbols`, `Files`, and `Repos` results as you type. Clicking on any result takes you to the definition. You can use the search type dropdown to show all types of results or limit it to a specific search type. - -[role="screenshot"] -image::images/code-quick-search.png[] - -[float] -==== Full-text search -If the quick search results don’t contain what you are looking for, you can press ‘Enter’ to conduct a full-text search. -[role="screenshot"] -image::images/code-full-text-search.png[] -You can further refine the results by using the repo and language filters on the left. - -[float] -==== Search filter -You can also use the Search Filters to limit the search scope to certain repos before issuing a query. To search across all repos, remove any applied repo filters. By default, search results are limited to the repo you're currently viewing. -[role="screenshot"] -image::images/code-search-filter.png[] - -include::code-multiple-kibana-instances-config.asciidoc[] diff --git a/docs/code/code-semantic-nav.asciidoc b/docs/code/code-semantic-nav.asciidoc deleted file mode 100644 index a1535f1449dfe1..00000000000000 --- a/docs/code/code-semantic-nav.asciidoc +++ /dev/null @@ -1,28 +0,0 @@ -[[code-semantic-nav]] - -== Semantic code navigation - -You can navigate a file with semantic code navigation features if: - -- *Code* supports the file's <> -- You have installed the corresponding <> - -[float] -==== Goto definition and find reference -Hovering your cursor over a symbol in a file opens information about the symbol, including its qualified name and documentation, when available. You can perform two actions: - -* *Goto Definition* navigates to the symbol definition. Definitions defined in a different repo can be found, provided that you have imported the repo with the definition. - -* *Find Reference* opens a panel that lists all references to the symbol. - -[role="screenshot"] -image::images/code-semantic-nav.png[] - -[float] -==== View symbol table -From the *Structure* tab, you can open a symbol table that details the structure of the current class. Clicking on a member function or variable jumps to its definition. - -[role="screenshot"] -image::images/code-symbol-table.png[] - -include::code-search.asciidoc[] diff --git a/docs/code/index.asciidoc b/docs/code/index.asciidoc deleted file mode 100644 index 799b356cb42c32..00000000000000 --- a/docs/code/index.asciidoc +++ /dev/null @@ -1,21 +0,0 @@ -[[code]] -= Code - -[partintro] --- - -beta[] - -Interaction with source code is pervasive and essential for any technology company. Speed of innovation is limited by how easy it is to search, navigate, and gain insight into your source code. -Elastic *Code* provides an easy-to-use code search solution that scales with your organization and empowers your team to understand your codebase faster than ever before. -*Code* offers the following functions: - -* Find references and definitions for any object or symbol -* Typeahead search for symbol definition, file, and repo -* Symbol table -* Full-text search with repo and language filters - -<> with *Code* by importing your first repo. --- - -include::code-import-first-repo.asciidoc[] diff --git a/docs/developer/core/development-modules.asciidoc b/docs/developer/core/development-modules.asciidoc index 603c0f2952d0cb..b36be6bbb5d25b 100644 --- a/docs/developer/core/development-modules.asciidoc +++ b/docs/developer/core/development-modules.asciidoc @@ -13,15 +13,6 @@ To prevent this from being an issue the ui module provides "autoloading" modules. The sole purpose of these modules is to extend the environment with certain components. Here is a breakdown of those modules: -- *`import 'ui/autoload/styles'`* - Imports all styles at the root of `src/legacy/ui/public/styles` - -- *`import 'ui/autoload/directives'`* - Imports all directives in `src/legacy/ui/public/directives` - -- *`import 'ui/autoload/filters'`* - Imports all filters in `src/legacy/ui/public/filters` - - *`import 'ui/autoload/modules'`* Imports angular and several ui services and "components" which Kibana depends on without importing. The full list of imports is hard coded in the diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.change.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.change.md new file mode 100644 index 00000000000000..eba149bf93a4c2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.change.md @@ -0,0 +1,34 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) > [change](./kibana-plugin-public.chromedoctitle.change.md) + +## ChromeDocTitle.change() method + +Changes the current document title. + +Signature: + +```typescript +change(newTitle: string | string[]): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newTitle | string | string[] | | + +Returns: + +`void` + +## Example + +How to change the title of the document + +```ts +chrome.docTitle.change('My application title') +chrome.docTitle.change(['My application', 'My section']) + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md new file mode 100644 index 00000000000000..3c6cfab4862881 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md @@ -0,0 +1,39 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) + +## ChromeDocTitle interface + +APIs for accessing and updating the document title. + +Signature: + +```typescript +export interface ChromeDocTitle +``` + +## Methods + +| Method | Description | +| --- | --- | +| [change(newTitle)](./kibana-plugin-public.chromedoctitle.change.md) | Changes the current document title. | +| [reset()](./kibana-plugin-public.chromedoctitle.reset.md) | Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) | + +## Example 1 + +How to change the title of the document + +```ts +chrome.docTitle.change('My application') + +``` + +## Example 2 + +How to reset the title of the document to it's initial value + +```ts +chrome.docTitle.reset() + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.reset.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.reset.md new file mode 100644 index 00000000000000..4b4c6f573e0061 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.reset.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) > [reset](./kibana-plugin-public.chromedoctitle.reset.md) + +## ChromeDocTitle.reset() method + +Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) + +Signature: + +```typescript +reset(): void; +``` +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.doctitle.md b/docs/development/core/public/kibana-plugin-public.chromestart.doctitle.md new file mode 100644 index 00000000000000..71eda64c24646e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromestart.doctitle.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeStart](./kibana-plugin-public.chromestart.md) > [docTitle](./kibana-plugin-public.chromestart.doctitle.md) + +## ChromeStart.docTitle property + +APIs for accessing and updating the document title. + +Signature: + +```typescript +docTitle: ChromeDocTitle; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md index bc41aa10cce8fa..153e06d591404d 100644 --- a/docs/development/core/public/kibana-plugin-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-public.chromestart.md @@ -16,6 +16,7 @@ export interface ChromeStart | Property | Type | Description | | --- | --- | --- | +| [docTitle](./kibana-plugin-public.chromestart.doctitle.md) | ChromeDocTitle | APIs for accessing and updating the document title. | | [navControls](./kibana-plugin-public.chromestart.navcontrols.md) | ChromeNavControls | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | | [navLinks](./kibana-plugin-public.chromestart.navlinks.md) | ChromeNavLinks | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. | | [recentlyAccessed](./kibana-plugin-public.chromestart.recentlyaccessed.md) | ChromeRecentlyAccessed | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md b/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md new file mode 100644 index 00000000000000..e94757c5eb031a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [anonymousPaths](./kibana-plugin-public.httpservicebase.anonymouspaths.md) + +## HttpServiceBase.anonymousPaths property + +APIs for denoting certain paths for not requiring authentication + +Signature: + +```typescript +anonymousPaths: IAnonymousPaths; +``` diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.md b/docs/development/core/public/kibana-plugin-public.httpservicebase.md index a1eef3db42b7dd..9ea77c95b343eb 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.md +++ b/docs/development/core/public/kibana-plugin-public.httpservicebase.md @@ -15,6 +15,7 @@ export interface HttpServiceBase | Property | Type | Description | | --- | --- | --- | +| [anonymousPaths](./kibana-plugin-public.httpservicebase.anonymouspaths.md) | IAnonymousPaths | APIs for denoting certain paths for not requiring authentication | | [basePath](./kibana-plugin-public.httpservicebase.basepath.md) | IBasePath | APIs for manipulating the basePath on URL segments. | | [delete](./kibana-plugin-public.httpservicebase.delete.md) | HttpHandler | Makes an HTTP request with the DELETE method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | | [fetch](./kibana-plugin-public.httpservicebase.fetch.md) | HttpHandler | Makes an HTTP request. Defaults to a GET request unless overriden. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | diff --git a/docs/development/core/public/kibana-plugin-public.ianonymouspaths.isanonymous.md b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.isanonymous.md new file mode 100644 index 00000000000000..d6be78e1e725b2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.isanonymous.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) > [isAnonymous](./kibana-plugin-public.ianonymouspaths.isanonymous.md) + +## IAnonymousPaths.isAnonymous() method + +Determines whether the provided path doesn't require authentication. `path` should include the current basePath. + +Signature: + +```typescript +isAnonymous(path: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | string | | + +Returns: + +`boolean` + diff --git a/docs/development/core/public/kibana-plugin-public.ianonymouspaths.md b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.md new file mode 100644 index 00000000000000..1290df28780cfe --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) + +## IAnonymousPaths interface + +APIs for denoting paths as not requiring authentication + +Signature: + +```typescript +export interface IAnonymousPaths +``` + +## Methods + +| Method | Description | +| --- | --- | +| [isAnonymous(path)](./kibana-plugin-public.ianonymouspaths.isanonymous.md) | Determines whether the provided path doesn't require authentication. path should include the current basePath. | +| [register(path)](./kibana-plugin-public.ianonymouspaths.register.md) | Register path as not requiring authentication. path should not include the current basePath. | + diff --git a/docs/development/core/public/kibana-plugin-public.ianonymouspaths.register.md b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.register.md new file mode 100644 index 00000000000000..3ab9bf438aa161 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.register.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) > [register](./kibana-plugin-public.ianonymouspaths.register.md) + +## IAnonymousPaths.register() method + +Register `path` as not requiring authentication. `path` should not include the current basePath. + +Signature: + +```typescript +register(path: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | string | | + +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index e787621c3aaf96..df0b963e2b6271 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -32,6 +32,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [Capabilities](./kibana-plugin-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. | | [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | | [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | +| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. | | [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | | | [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | | [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | | @@ -57,6 +58,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpResponse](./kibana-plugin-public.httpresponse.md) | | | [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | | | [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | +| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | | [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | | [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | | [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | | @@ -119,5 +121,5 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md). | | [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) | | [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) | -| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | +| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md index 5eaf06e7dd6820..06daf8e8151cd3 100644 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md +++ b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md @@ -9,9 +9,9 @@ Gets the metadata about all uiSettings, including the type, default value, and u Signature: ```typescript -getAll(): UiSettingsState; +getAll(): Record>; ``` Returns: -`UiSettingsState` +`Record>` diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md b/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md index 6eda1fd3274c60..7173386d882652 100644 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md +++ b/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md @@ -4,7 +4,7 @@ ## UiSettingsClientContract type -[UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) +Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) Signature: diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index a6dda69fd154ef..c51459bc41a434 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -19,4 +19,5 @@ export interface CoreSetup | [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | +| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.uisettings.md b/docs/development/core/server/kibana-plugin-server.coresetup.uisettings.md new file mode 100644 index 00000000000000..54120d7c3fa8da --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.uisettings.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) + +## CoreSetup.uiSettings property + +[UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) + +Signature: + +```typescript +uiSettings: UiSettingsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md deleted file mode 100644 index 29faa6d945b43c..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) - -## IUiSettingsClient.getDefaults property - -Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) - -Signature: - -```typescript -getDefaults: () => Record; -``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getregistered.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getregistered.md new file mode 100644 index 00000000000000..16ae4c3dd8b364 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getregistered.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getRegistered](./kibana-plugin-server.iuisettingsclient.getregistered.md) + +## IUiSettingsClient.getRegistered property + +Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) + +Signature: + +```typescript +getRegistered: () => Readonly>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md index 9a449b64ed5d03..134039cfa91f3f 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md @@ -9,8 +9,5 @@ Retrieves a set of all uiSettings values set by the user. Signature: ```typescript -getUserProvided: () => Promise>; +getUserProvided: () => Promise>>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md index 142f33d27c3850..a4697ddbbb85ed 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md @@ -4,7 +4,7 @@ ## IUiSettingsClient interface -Service that provides access to the UiSettings stored in elasticsearch. +Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. Signature: @@ -18,8 +18,8 @@ export interface IUiSettingsClient | --- | --- | --- | | [get](./kibana-plugin-server.iuisettingsclient.get.md) | <T extends SavedObjectAttribute = any>(key: string) => Promise<T> | Retrieves uiSettings values set by the user with fallbacks to default values if not specified. | | [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>> | Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. | -| [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) | () => Record<string, UiSettingsParams> | Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | -| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, {
userValue?: T;
isOverridden?: boolean;
}>> | Retrieves a set of all uiSettings values set by the user. | +| [getRegistered](./kibana-plugin-server.iuisettingsclient.getregistered.md) | () => Readonly<Record<string, UiSettingsParams>> | Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | +| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, UserProvidedValues<T>>> | Retrieves a set of all uiSettings values set by the user. | | [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | | [remove](./kibana-plugin-server.iuisettingsclient.remove.md) | (key: string) => Promise<void> | Removes uiSettings value by key. | | [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) | (keys: string[]) => Promise<void> | Removes multiple uiSettings values by keys. | diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 2f81afacf4bb46..9907750b8742f0 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -63,7 +63,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | | [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | -| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Service that provides access to the UiSettings stored in elasticsearch. | +| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | | [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | | [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | | [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | @@ -90,10 +90,12 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | | | [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | | | [SavedObjectsBulkUpdateObject](./kibana-plugin-server.savedobjectsbulkupdateobject.md) | | +| [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) | | | [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | | | [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | | [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | | [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | +| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | | [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | | [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | | [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | @@ -116,6 +118,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | | [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | +| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | +| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | ## Variables @@ -149,6 +153,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | | [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-server.migration_assistance_index_action.md) | | | [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-server.migration_deprecation_level.md) | | +| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | | [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | | [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | diff --git a/docs/development/core/server/kibana-plugin-server.mutatingoperationrefreshsetting.md b/docs/development/core/server/kibana-plugin-server.mutatingoperationrefreshsetting.md new file mode 100644 index 00000000000000..94c8fa8c22ef62 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.mutatingoperationrefreshsetting.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) + +## MutatingOperationRefreshSetting type + +Elasticsearch Refresh setting for mutating operation + +Signature: + +```typescript +export declare type MutatingOperationRefreshSetting = boolean | 'wait_for'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md index e1ea358320b9a6..2d8b27ecb6c670 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md @@ -15,5 +15,8 @@ core: { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; }; + uiSettings: { + client: IUiSettingsClient; + }; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md index 37a40f98adef3b..c9fc80596efa9d 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
} | | +| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
} | | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.md new file mode 100644 index 00000000000000..920a6ca224df49 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) + +## SavedObjectsBulkUpdateOptions interface + + +Signature: + +```typescript +export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md new file mode 100644 index 00000000000000..35e9e6483da10e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) > [refresh](./kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md) + +## SavedObjectsBulkUpdateOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md index 107e71959f7062..30db524ffa02c8 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md @@ -9,7 +9,7 @@ Bulk Updates multiple SavedObject at once Signature: ```typescript -bulkUpdate(objects: Array>, options?: SavedObjectsBaseOptions): Promise>; +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; ``` ## Parameters @@ -17,7 +17,7 @@ bulkUpdate(objects: ArrayArray<SavedObjectsBulkUpdateObject<T>> | | -| options | SavedObjectsBaseOptions | | +| options | SavedObjectsBulkUpdateOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md index 657f56d591e772..c20c7e886490a4 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md @@ -9,7 +9,7 @@ Deletes a SavedObject Signature: ```typescript -delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}>; +delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; ``` ## Parameters @@ -18,7 +18,7 @@ delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}> | --- | --- | --- | | type | string | | | id | string | | -| options | SavedObjectsBaseOptions | | +| options | SavedObjectsDeleteOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md index 16549e420ac01e..e4ad6360569152 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md @@ -19,4 +19,5 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions | [migrationVersion](./kibana-plugin-server.savedobjectscreateoptions.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [overwrite](./kibana-plugin-server.savedobjectscreateoptions.overwrite.md) | boolean | Overwrite existing documents (defaults to false) | | [references](./kibana-plugin-server.savedobjectscreateoptions.references.md) | SavedObjectReference[] | | +| [refresh](./kibana-plugin-server.savedobjectscreateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.refresh.md new file mode 100644 index 00000000000000..785874a12c8c45 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) > [refresh](./kibana-plugin-server.savedobjectscreateoptions.refresh.md) + +## SavedObjectsCreateOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.md new file mode 100644 index 00000000000000..2c641ba5cc8d8e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) + +## SavedObjectsDeleteOptions interface + + +Signature: + +```typescript +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsdeleteoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.refresh.md new file mode 100644 index 00000000000000..782c52956f2976 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) > [refresh](./kibana-plugin-server.savedobjectsdeleteoptions.refresh.md) + +## SavedObjectsDeleteOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md index 7fcd362e937a01..49e8946ad28269 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md @@ -16,5 +16,6 @@ export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions | Property | Type | Description | | --- | --- | --- | | [references](./kibana-plugin-server.savedobjectsupdateoptions.references.md) | SavedObjectReference[] | A reference to another saved object. | +| [refresh](./kibana-plugin-server.savedobjectsupdateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | | [version](./kibana-plugin-server.savedobjectsupdateoptions.version.md) | string | An opaque version number which changes on each successful write operation. Can be used for implementing optimistic concurrency control. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.refresh.md new file mode 100644 index 00000000000000..bb1142c2420120 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) > [refresh](./kibana-plugin-server.savedobjectsupdateoptions.refresh.md) + +## SavedObjectsUpdateOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md index 47aedbfbf28106..6bf1b17dc947a4 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md @@ -9,5 +9,5 @@ used to group the configured setting in the UI Signature: ```typescript -category: string[]; +category?: string[]; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md index 8d8887285ae2e2..6a203629f5425c 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md @@ -9,5 +9,5 @@ description provided to a user in UI Signature: ```typescript -description: string; +description?: string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md index 275111c05eff92..a38499e8f37dda 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md @@ -20,7 +20,7 @@ export interface UiSettingsParams | [description](./kibana-plugin-server.uisettingsparams.description.md) | string | description provided to a user in UI | | [name](./kibana-plugin-server.uisettingsparams.name.md) | string | title in the UI | | [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | Record<string, string> | text labels for 'select' type UI element | -| [options](./kibana-plugin-server.uisettingsparams.options.md) | string[] | a range of valid values | +| [options](./kibana-plugin-server.uisettingsparams.options.md) | string[] | array of permitted values for this setting | | [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | | [type](./kibana-plugin-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md index 2b414eefffed2b..07905ca7de20af 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md @@ -9,5 +9,5 @@ title in the UI Signature: ```typescript -name: string; +name?: string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md index 71eecdfabc4a0c..2220feab74ffde 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md @@ -4,7 +4,7 @@ ## UiSettingsParams.options property -a range of valid values +array of permitted values for this setting Signature: diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md index 455756899ecfca..397498ccf5c111 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md @@ -9,5 +9,5 @@ default value to fall back to if a user doesn't provide any Signature: ```typescript -value: SavedObjectAttribute; +value?: SavedObjectAttribute; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md new file mode 100644 index 00000000000000..8dde78f633d883 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) + +## UiSettingsServiceSetup interface + + +Signature: + +```typescript +export interface UiSettingsServiceSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [register(settings)](./kibana-plugin-server.uisettingsservicesetup.register.md) | Sets settings with default values for the uiSettings. | + diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md new file mode 100644 index 00000000000000..8091a7cec44aaf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) > [register](./kibana-plugin-server.uisettingsservicesetup.register.md) + +## UiSettingsServiceSetup.register() method + +Sets settings with default values for the uiSettings. + +Signature: + +```typescript +register(settings: Record): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| settings | Record<string, UiSettingsParams> | | + +Returns: + +`void` + +## Example + +setup(core: CoreSetup){ core.uiSettings.register(\[{ foo: { name: i18n.translate('my foo settings'), value: true, description: 'add some awesomeness', }, }\]); } + diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.isoverridden.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.isoverridden.md new file mode 100644 index 00000000000000..01e04b490595d5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.isoverridden.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) > [isOverridden](./kibana-plugin-server.userprovidedvalues.isoverridden.md) + +## UserProvidedValues.isOverridden property + +Signature: + +```typescript +isOverridden?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md new file mode 100644 index 00000000000000..7b2114404d7f2e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) + +## UserProvidedValues interface + +Describes the values explicitly set by user. + +Signature: + +```typescript +export interface UserProvidedValues +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [isOverridden](./kibana-plugin-server.userprovidedvalues.isoverridden.md) | boolean | | +| [userValue](./kibana-plugin-server.userprovidedvalues.uservalue.md) | T | | + diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.uservalue.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.uservalue.md new file mode 100644 index 00000000000000..59d25651b7697d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.uservalue.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) > [userValue](./kibana-plugin-server.userprovidedvalues.uservalue.md) + +## UserProvidedValues.userValue property + +Signature: + +```typescript +userValue?: T; +``` diff --git a/docs/discover/kuery.asciidoc b/docs/discover/kuery.asciidoc index a87b8d5a78604b..abf3e05fb78198 100644 --- a/docs/discover/kuery.asciidoc +++ b/docs/discover/kuery.asciidoc @@ -73,3 +73,87 @@ set these terms will be matched against all fields. For example, a query for `re in the response field, but a query for just `200` will search for 200 across all fields in your index. ============ +===== Nested Field Support + +KQL supports querying on {ref}/nested.html[nested fields] through a special syntax. You can query nested fields in subtly different +ways, depending on the results you want, so crafting nested queries requires extra thought. + +One main consideration is how to match parts of the nested query to the individual nested documents. +There are two main approaches to take: + +* *Parts of the query may only match a single nested document.* This is what most users want when querying on a nested field. +* *Parts of the query can match different nested documents.* This is how a regular object field works. + Although generally less useful, there might be occasions where you want to query a nested field in this way. + +Let's take a look at the first approach. In the following document, `items` is a nested field: + +[source,json] +---------------------------------- +{ + "grocery_name": "Elastic Eats", + "items": [ + { + "name": "banana", + "stock": "12", + "category": "fruit" + }, + { + "name": "peach", + "stock": "10", + "category": "fruit" + }, + { + "name": "carrot", + "stock": "9", + "category": "vegetable" + }, + { + "name": "broccoli", + "stock": "5", + "category": "vegetable" + } + ] +} +---------------------------------- + +To find stores that have more than 10 bananas in stock, you would write a query like this: + +`items:{ name:banana and stock > 10 }` + +`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single document. +For example, `items:{ name:banana and stock:9 }` does not match because there isn't a single nested document that +matches the entire query in the nested group. + +What if you want to find a store with more than 10 bananas that *also* stocks vegetables? This is the second way of querying a nested field, and you can do it like this: + +`items:{ name:banana and stock > 10 } and items:{ category:vegetable }` + +The first nested group (`name:banana and stock > 10`) must still match a single document, but the `category:vegetables` +subquery can match a different nested document because it is in a separate group. + +KQL's syntax also supports nested fields inside of other nested fields—you simply have to specify the full path. Suppose you +have a document where `level1` and `level2` are both nested fields: + +[source,json] +---------------------------------- +{ + "level1": [ + { + "level2": [ + { + "prop1": "foo", + "prop2": "bar" + }, + { + "prop1": "baz", + "prop2": "qux" + } + ] + } + ] +} +---------------------------------- + +You can match on a single nested document by specifying the full path: + +`level1.level2:{ prop1:foo and prop2:bar }` diff --git a/docs/getting-started/tutorial-full-experience.asciidoc b/docs/getting-started/tutorial-full-experience.asciidoc index 08f65b0a240911..eafbb7d8f7c918 100644 --- a/docs/getting-started/tutorial-full-experience.asciidoc +++ b/docs/getting-started/tutorial-full-experience.asciidoc @@ -183,7 +183,7 @@ At this point, you're ready to use the Elasticsearch {ref}/docs-bulk.html[bulk] API to load the data sets: [source,shell] -curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/bank/account/_bulk?pretty' --data-binary @accounts.json +curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/bank/_bulk?pretty' --data-binary @accounts.json curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/shakespeare/_bulk?pretty' --data-binary @shakespeare.json curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/_bulk?pretty' --data-binary @logs.jsonl diff --git a/docs/images/canvas-create-URL.gif b/docs/images/canvas-create-URL.gif new file mode 100644 index 00000000000000..c7dfab28c681e6 Binary files /dev/null and b/docs/images/canvas-create-URL.gif differ diff --git a/docs/images/canvas-embed_workpad.gif b/docs/images/canvas-embed_workpad.gif new file mode 100644 index 00000000000000..0ffbdeab2862d6 Binary files /dev/null and b/docs/images/canvas-embed_workpad.gif differ diff --git a/docs/images/canvas-export-workpad.png b/docs/images/canvas-export-workpad.png index 687c8a121b65ad..fa910daf948d76 100644 Binary files a/docs/images/canvas-export-workpad.png and b/docs/images/canvas-export-workpad.png differ diff --git a/docs/images/canvas-generate-pdf.gif b/docs/images/canvas-generate-pdf.gif index 6d60f948de77a5..a51e04cb623a6b 100644 Binary files a/docs/images/canvas-generate-pdf.gif and b/docs/images/canvas-generate-pdf.gif differ diff --git a/docs/images/canvas_share_autoplay_480.gif b/docs/images/canvas_share_autoplay_480.gif new file mode 100644 index 00000000000000..da6d8c74dd5ca9 Binary files /dev/null and b/docs/images/canvas_share_autoplay_480.gif differ diff --git a/docs/images/canvas_share_hidetoolbar_480.gif b/docs/images/canvas_share_hidetoolbar_480.gif new file mode 100644 index 00000000000000..6206714aa28048 Binary files /dev/null and b/docs/images/canvas_share_hidetoolbar_480.gif differ diff --git a/docs/images/code-blame.png b/docs/images/code-blame.png deleted file mode 100644 index c04e1b12c58f58..00000000000000 Binary files a/docs/images/code-blame.png and /dev/null differ diff --git a/docs/images/code-full-text-search.png b/docs/images/code-full-text-search.png deleted file mode 100644 index 4d4d3d21a24f6f..00000000000000 Binary files a/docs/images/code-full-text-search.png and /dev/null differ diff --git a/docs/images/code-history.png b/docs/images/code-history.png deleted file mode 100644 index 853fcc8fa6adcb..00000000000000 Binary files a/docs/images/code-history.png and /dev/null differ diff --git a/docs/images/code-import-repo.png b/docs/images/code-import-repo.png deleted file mode 100644 index adc60614c10077..00000000000000 Binary files a/docs/images/code-import-repo.png and /dev/null differ diff --git a/docs/images/code-lang-server-status.png b/docs/images/code-lang-server-status.png deleted file mode 100644 index 3143a876f1c759..00000000000000 Binary files a/docs/images/code-lang-server-status.png and /dev/null differ diff --git a/docs/images/code-lang-server-tab.png b/docs/images/code-lang-server-tab.png deleted file mode 100644 index b25573f30d40ba..00000000000000 Binary files a/docs/images/code-lang-server-tab.png and /dev/null differ diff --git a/docs/images/code-quick-search.png b/docs/images/code-quick-search.png deleted file mode 100644 index e6274b77dcbaa9..00000000000000 Binary files a/docs/images/code-quick-search.png and /dev/null differ diff --git a/docs/images/code-repo-management.png b/docs/images/code-repo-management.png deleted file mode 100644 index 6e3fe5476b3be9..00000000000000 Binary files a/docs/images/code-repo-management.png and /dev/null differ diff --git a/docs/images/code-search-filter.png b/docs/images/code-search-filter.png deleted file mode 100644 index f9b09ad2d71ec6..00000000000000 Binary files a/docs/images/code-search-filter.png and /dev/null differ diff --git a/docs/images/code-semantic-nav.png b/docs/images/code-semantic-nav.png deleted file mode 100644 index 664225804ce221..00000000000000 Binary files a/docs/images/code-semantic-nav.png and /dev/null differ diff --git a/docs/images/code-starter-root.png b/docs/images/code-starter-root.png deleted file mode 100644 index a2e3a579fe2f26..00000000000000 Binary files a/docs/images/code-starter-root.png and /dev/null differ diff --git a/docs/images/code-symbol-table.png b/docs/images/code-symbol-table.png deleted file mode 100644 index d3bb4c6eef0c1b..00000000000000 Binary files a/docs/images/code-symbol-table.png and /dev/null differ diff --git a/docs/infrastructure/getting-started.asciidoc b/docs/infrastructure/getting-started.asciidoc index 151a8e2928cf8f..7122ad5c19f752 100644 --- a/docs/infrastructure/getting-started.asciidoc +++ b/docs/infrastructure/getting-started.asciidoc @@ -1,11 +1,11 @@ [role="xpack"] [[xpack-metrics-getting-started]] -== Getting started with infrastructure monitoring +== Getting started with metrics To get started with the Metrics app in Kibana, you need to start collecting metrics data for your infrastructure. Kibana provides step-by-step instructions to help you add metrics data. -The {infra-guide}[Infrastructure Monitoring Guide] is a good source for more detailed information and instructions. +The {metrics-guide}[Metrics Monitoring Guide] is a good source for more detailed information and instructions. [role="screenshot"] -image::infrastructure/images/metrics-add-data.png[Screenshot showing Add metric data to Kibana UI] +image::infrastructure/images/metrics-add-data.png[Screenshot showing Add metric data to Kibana] diff --git a/docs/infrastructure/index.asciidoc b/docs/infrastructure/index.asciidoc index 17361ef6a6080d..5e2d0f3e757b09 100644 --- a/docs/infrastructure/index.asciidoc +++ b/docs/infrastructure/index.asciidoc @@ -4,13 +4,13 @@ [partintro] -- -The Metrics app enables you to monitor your infrastructure and identify problems in real time. +The Metrics app enables you to monitor your infrastructure metrics and identify problems in real time. You start with a visual summary of your infrastructure where you can view basic metrics for common servers, containers, and services. Then you can drill down to view more detailed metrics or other information for that component. You can: -* View an inventory of your infrastructure by hosts, Kubernetes pod or Docker containers. +* View your infrastructure metrics by hosts, Kubernetes pods or Docker containers. You can group and filter the data in various ways to help you identify the items that interest you. * View current and historic values for metrics such as CPU usage, memory usage, and network traffic for each component. diff --git a/docs/infrastructure/infra-ui.asciidoc b/docs/infrastructure/infra-ui.asciidoc index 5c8c50a978d63d..120a22541717cd 100644 --- a/docs/infrastructure/infra-ui.asciidoc +++ b/docs/infrastructure/infra-ui.asciidoc @@ -2,12 +2,12 @@ [[infra-ui]] == Using the Metrics app -Use the Metrics app in {kib} to monitor your infrastructure and identify problems in real time. +Use the Metrics app in {kib} to monitor your infrastructure metrics and identify problems in real time. You can explore metrics for hosts, containers, and services. You can also drill down to view more detailed metrics, or seamlessly switch to view the corresponding logs, application traces, and uptime information. Initially, the *Inventory* tab shows an overview of the hosts in of your infrastructure and the current CPU usage for each host. -From here, you can drill down into areas of interest. +From here, you can view other metrics or drill down into areas of interest. [role="screenshot"] image::infrastructure/images/infra-sysmon.png[Infrastructure Overview in Kibana] diff --git a/docs/infrastructure/metrics-explorer.asciidoc b/docs/infrastructure/metrics-explorer.asciidoc index 2919eaa976d6a7..c20718dac1c7ac 100644 --- a/docs/infrastructure/metrics-explorer.asciidoc +++ b/docs/infrastructure/metrics-explorer.asciidoc @@ -15,7 +15,7 @@ image::infrastructure/images/metrics-explorer-screen.png[Metrics Explorer in Kib * Metrics Explorer uses data collected from {metricbeat-ref}/metricbeat-overview.html[Metricbeat]. * You need read permissions on `metricbeat-*` or the metric index specified in the Metrics configuration. -* Metrics Explorer uses the timestamp field set in the Infrastructure configuration. +* Metrics Explorer uses the timestamp field from the *Settings* tab. By default that is set to `@timestamp`. * The interval for the X Axis is set to `auto`. The bucket size is determined by the time range. diff --git a/docs/logs/getting-started.asciidoc b/docs/logs/getting-started.asciidoc index 1ed8798a4b87fb..ca09bb34c0e56a 100644 --- a/docs/logs/getting-started.asciidoc +++ b/docs/logs/getting-started.asciidoc @@ -5,7 +5,7 @@ To get started with the Logs app in Kibana, you need to start collecting logs data for your infrastructure. Kibana provides step-by-step instructions to help you add logs data. -The {infra-guide}[Infrastructure Monitoring Guide] is a good source for more detailed information and instructions. +The {logs-guide}[Logs Monitoring Guide] is a good source for more detailed information and instructions. [role="screenshot"] image::logs/images/logs-add-data.png[Screenshot showing Add logging data in Kibana] diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc index 65693f4399e53c..916ad42a6d221d 100644 --- a/docs/logs/using.asciidoc +++ b/docs/logs/using.asciidoc @@ -17,7 +17,7 @@ image::logs/images/logs-console.png[Logs Console in Kibana] Use the search bar to perform ad hoc searches for specific text. You can also create structured queries using {kibana-ref}/kuery-query.html[Kibana Query Language]. For example, enter `host.hostname : "host1"` to see only the information for `host1`. -// ++ this isn't quite the same as the corresponding infrastructure description now. +// ++ this isn't quite the same as the corresponding metrics description now. [float] [[logs-configure-source]] diff --git a/docs/management/snapshot-restore/images/create-policy-example.png b/docs/management/snapshot-restore/images/create-policy-example.png new file mode 100755 index 00000000000000..e871c925f5fd52 Binary files /dev/null and b/docs/management/snapshot-restore/images/create-policy-example.png differ diff --git a/docs/management/snapshot-restore/images/snapshot-retention.png b/docs/management/snapshot-restore/images/snapshot-retention.png new file mode 100755 index 00000000000000..7b390357a21b6b Binary files /dev/null and b/docs/management/snapshot-restore/images/snapshot-retention.png differ diff --git a/docs/management/snapshot-restore/index.asciidoc b/docs/management/snapshot-restore/index.asciidoc index f309b2c17e0ed5..f19aaa122675e7 100644 --- a/docs/management/snapshot-restore/index.asciidoc +++ b/docs/management/snapshot-restore/index.asciidoc @@ -11,11 +11,11 @@ you can restore a snapshot from the repository. You’ll find *Snapshot and Restore* under *Management > Elasticsearch*. With this UI, you can: -* <> -* <> -* <> -* <> -* <> +* Register a repository for storing your snapshots +* View a list of your snapshots and drill down into details +* Restore data into your cluster from a snapshot +* Create a policy to automate snapshot creation and deletion +* Delete a snapshot to free storage space [role="screenshot"] image:management/snapshot-restore/images/snapshot_list.png["Snapshot list"] @@ -27,28 +27,34 @@ more detailed information. [float] [[kib-snapshot-register-repository]] === Register a repository +A repository is where your snapshots live. You must register a snapshot +repository before you can perform snapshot and restore operations. + +If you don't have a repository, Kibana walks you through the process of +registering one. +{kib} supports three repository types +out of the box: shared file system, read-only URL, and source-only. +For more information on these repositories and their settings, +see {ref}/modules-snapshots.html#snapshots-repositories[Repositories]. +To use other repositories, such as S3, see +{ref}/modules-snapshots.html#_repository_plugins[Repository plugins]. -The *Repositories* view provides an overview of your repositories. -Click a repository name to view its type, number of snapshots, and settings, and also to verify status. + +Once you create a repository, it is listed in the *Repositories* +view. +Click a repository name to view its type, number of snapshots, and settings, +and to verify status. [role="screenshot"] image:management/snapshot-restore/images/repository_list.png["Repository list"] -If you don't have a repository, you're prompted to register one. -{es} supports three repository types -out of the box: shared file system, read-only URL, and source-only. -For more information on these repositories and their settings, -see {ref}/modules-snapshots.html#snapshots-repositories[Repositories]. For an example, -see <>. - -To use other repositories, such as S3, you can install plugins. See -{ref}/modules-snapshots.html#_repository_plugins[Repository plugins]. [float] [[kib-view-snapshot]] === View your snapshots -The *Snapshots* view gives an overview of your snapshots. You can drill down +A snapshot is a backup taken from a running {es} cluster. You'll find an overview of +your snapshots in the *Snapshots* view, and you can drill down into each snapshot for further investigation. [role="screenshot"] @@ -68,18 +74,25 @@ the new data. [[kib-restore-snapshot]] === Restore a snapshot -The *Restore* wizard walks you through the process of restoring a snapshot -into a running cluster. To get started, go to the *Snapshots* view, find the -snapshot, and click the restore icon in the *Actions* column. +The information stored in a snapshot is not tied to a specific +cluster or a cluster name. This enables you to +restore a snapshot made from one cluster to another cluster. You might +use the restore operation to: -You’re presented -options for the restore, including which +* Recover data lost due to a failure +* Migrate a current Elasticsearch cluster to a new version +* Move data from one cluster to another cluster + +To get started, go to the *Snapshots* view, find the +snapshot, and click the restore icon in the *Actions* column. +The Restore wizard presents +options for the restore operation, including which indices to restore and whether to modify the index settings. You can restore an existing index only if it’s closed and has the same number of shards as the index in the snapshot. Once you initiate the restore, you're navigated to the *Restore Status* view, -where you can track the progress. +where you can track the current state for each shard in the snapshot. [role="screenshot"] image:management/snapshot-restore/images/snapshot-restore.png["Snapshot details"] @@ -89,23 +102,28 @@ image:management/snapshot-restore/images/snapshot-restore.png["Snapshot details" [[kib-snapshot-policy]] === Create a snapshot lifecycle policy -You can create policies to schedule automatic snapshots of your cluster. -{ref}/snapshot-lifecycle-management-api.html[Snapshot lifecycle policies] are related -to {ref}/index-lifecycle-management.html[index lifecycle policies]. -However, where an index lifecycle policy applies to a single index, -a snapshot lifecycle policy can span multiple indices. - -For an overview of your policies, open the *Policies* view. -You can drill down into each policy to examine its settings and last successful and failed run. - -If you don’t have any policies, use the *Create policy* wizard. -You’ll define the snapshots and repository, when to take snapshots, and -the settings, such as which indices the snapshot should contain. +Use a {ref}/snapshot-lifecycle-management-api.html[snapshot lifecycle policy] +to automate the creation and deletion +of cluster snapshots. Taking automatic snapshots: + +* Ensures your {es} indices and clusters are backed up on a regular basis +* Ensures a recent and relevant snapshot is available if a situation +arises where a cluster needs to be recovered +* Allows you to manage your snapshots in {kib}, instead of using a +third-party tool + +If you don’t have any snapshot policies, follow the +*Create policy* wizard. It walks you through defining +when and where to take snapshots, the settings you want, +and how long to retain snapshots. [role="screenshot"] -image:management/snapshot-restore/images/create-policy.png["Snapshot details"] +image:management/snapshot-restore/images/snapshot-retention.png["Snapshot details"] + +An overview of your policies is on the *Policies* view. +You can drill down into each policy to examine its settings and last successful and failed run. -You can perform the following actions on a policy: +You can perform the following actions on a snapshot policy: * *Run* a policy immediately without waiting for the scheduled time. This action is useful before an upgrade or before performing maintenance on indices. @@ -113,6 +131,9 @@ This action is useful before an upgrade or before performing maintenance on indi * *Delete* a policy to prevent any future snapshots from being taken. This action does not cancel any currently ongoing snapshots or remove any previously taken snapshots. +[role="screenshot"] +image:management/snapshot-restore/images/create-policy.png["Snapshot details"] + [float] [[kib-delete-snapshot]] === Delete a snapshot @@ -123,16 +144,25 @@ Find the snapshot in the *Snapshots* view and click the trash icon in the and then click *Delete snapshots*. [[snapshot-repositories-example]] -[float] -=== Example: Register a shared file system repository -This example shows how to register a shared file system repository -and store snapshots. +[role="xpack"] +[[snapshot-restore-tutorial]] +=== Tutorial: Snapshot and Restore -[float] -==== Register the repository location -You must register the location of the repository in the `path.repo` setting on +Ready to try *Snapshot and Restore*? In this tutorial, you'll learn to: + +* Register a repository +* Add snapshots to the repository +* Create a snapshot lifecycle policy +* Restore a snapshot + +==== Before you begin + +This example shows you how to register a shared file system repository +and store snapshots. +Before you begin, you must register the location of the repository in the +{ref}/modules-snapshots.html#_shared_file_system_repository[path.repo] setting on your master and data nodes. You can do this in one of two ways: * Edit your `elasticsearch.yml` to include the `path.repo` setting. @@ -142,30 +172,26 @@ your master and data nodes. You can do this in one of two ways: `bin/elasticsearch -E path.repo=/tmp/es-backups` [float] -==== Register the repository +[[register-repo-example]] +==== Register a repository Use *Snapshot and Restore* to register the repository where your snapshots will live. . Go to *Management > Elasticsearch > Snapshot and Restore*. -. Open the *Repositories* view. -. Click *Register a repository*. +. Click *Register a repository* in either the introductory message or *Repository view*. . Enter a name for your repository, for example, `my_backup`. -. Set *Repository type* to Shared file system. +. Select *Shared file system*. + [role="screenshot"] image:management/snapshot-restore/images/register_repo.png["Register repository"] . Click *Next*. -. In *Location*, enter the path to the snapshot repository, `/tmp/es-backups`. -. In *Chunk size*, enter 100mb so that snapshot files are not bigger than that size. -. Use the defaults for all other fields. -. Click *Register*. +. In *File system location*, enter the path to the snapshot repository, `/tmp/es-backups`. +. In *Chunk size*, enter `100mb` so that snapshot files are not bigger than that size. +. Use the defaults for all other fields, and then click *Register*. + Your new repository is listed on the *Repositories* view. -+ -. Click the respository and inspect its details. -+ The repository currently doesn’t have any snapshots. @@ -174,19 +200,105 @@ The repository currently doesn’t have any snapshots. Use the {ref}//modules-snapshots.html#snapshots-take-snapshot[snapshot API] to create a snapshot. . Go to *Dev Tools > Console*. -. Create the snapshot. +. Create the snapshot: ++ +[source,js] +PUT /_snapshot/my_backup/2019-04-25_snapshot?wait_for_completion=true + In this example, the snapshot name is `2019-04-25_snapshot`. You can also use {ref}//date-math-index-names.html[date math expression] for the snapshot name. + [role="screenshot"] image:management/snapshot-restore/images/create_snapshot.png["Create snapshot"] -+ -. Open *Snapshot and Restore*. + +. Return to *Snapshot and Restore*. + Your new snapshot is available in the *Snapshots* view. +[[create-policy-example]] +==== Create a snapshot lifecycle policy + +Now you'll automate the creation and deletion of snapshots +using the repository created in the previous example. + +. Open the *Policies* view. +. Click *Create a policy*. ++ +[role="screenshot"] +image:management/snapshot-restore/images/create-policy-example.png["Create policy wizard"] + +. As you walk through the wizard, enter the following values: ++ +|=== +|*Logistics* | + +|Policy name +|`daily-snapshots` + +|Snapshot name +|`` + +|Schedule +|Every day at 1:30 a.m. + +|Repository +|`my_backup` + +|*Snapshot settings* | +|Indices +|Select the indices to back up. By default, all indices, including system indices, are backed up. +|All other settings +|Use the defaults. +|*Snapshot retention* | + +|Expiration +|`30 days` + +|Snapshots to retain +|Minimum count: `5`, Maximum count: `50` +|=== + +. Review your input, and then click *Create policy*. ++ +Your new policy is listed in the *Policies* view, and you see a summary of its details. + +[[restore-snapshot-example]] +==== Restore a snapshot +Finally, you'll restore indices from an existing snapshot. + +. In the *Snapshots* view, find the snapshot you want to restore, for example `2019-04-25_snapshot`. +. Click the restore icon in the *Actions* column. +. As you walk through the wizard, enter the following values: ++ +|=== +|*Logistics* | + +|Indices +|Toggle to choose specific indices to restore, or leave in place to restore all indices. + +|Rename indices +|Toggle to give your restored indices new names, or leave in place to restore under original index names. + +|All other fields +|Use the defaults. + +|*Index settings* | + +|Modify index settings +|Toggle to overwrite index settings when they are restored, +or leave in place to keep existing settings. + +|Reset index settings +|Toggle to reset index settings back to the default when they are restored, +or leave in place to keep existing settings. +|=== + +. Review your restore settings, and then click *Restore snapshot*. ++ +The operation loads for a few seconds, +and then you’re navigated to *Restore Status*, +where you can monitor the status of your restored indices. diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index d9b21f591f6256..8c3cb371b6addf 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -9,7 +9,7 @@ for example, in visualizations and Canvas workpads. [float] === Why GeoJSON? GeoJSON is an open-standard file format for storing geospatial vector data. -Although many vector data formats are available in the GIS community, +Although many vector data formats are available in the GIS community, GeoJSON is the most commonly used and flexible option. [float] @@ -18,14 +18,14 @@ Follow the instructions below to upload a GeoJSON data file, or try the <>. . Open *Elastic Maps*, and then click *Add layer*. -. Click *Upload GeoJSON vector file*. +. Click *Uploaded GeoJSON*. + [role="screenshot"] image::maps/images/fu_gs_select_source_file_upload.png[] . Use the file chooser to select a valid GeoJSON file. The file will load a preview of the data on the map. -. Use the default *Index type* of {ref}/geo-point.html[geo_point] for point data, +. Use the default *Index type* of {ref}/geo-point.html[geo_point] for point data, or override it and select {ref}/geo-shape.html[geo_shape]. All other shapes will default to a type of `geo_shape`. . Leave the default *Index name* and *Index pattern* names (the name of the uploaded diff --git a/docs/maps/heatmap-layer.asciidoc b/docs/maps/heatmap-layer.asciidoc index 9d456c59b69ef5..77b6d929a931cc 100644 --- a/docs/maps/heatmap-layer.asciidoc +++ b/docs/maps/heatmap-layer.asciidoc @@ -13,6 +13,6 @@ You can create a heat map layer from the following data source: Set *Show as* to *heat map*. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. -NOTE: Only count and sum metric aggregations are available with the grid aggregation source and heat map layers. -Mean, median, min, and max are turned off because the heat map will blend nearby values. +NOTE: Only count, sum, unique count metric aggregations are available with the grid aggregation source and heat map layers. +Average, min, and max are turned off because the heat map will blend nearby values. Blending two average values would make the cluster more prominent, even though it just might literally mean that these nearby areas are average. diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index 5645567ec7e79e..5284bd9ac2ac55 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -2,7 +2,7 @@ [[indexing-geojson-data-tutorial]] == Indexing GeoJSON data tutorial -In this tutorial, you'll build a customized map that shows the flight path between +In this tutorial, you'll build a customized map that shows the flight path between two airports, and the lightning hot spots on that route. You'll learn to: * Import GeoJSON files into Kibana @@ -15,7 +15,7 @@ two airports, and the lightning hot spots on that route. You'll learn to: This tutorial requires you to download the following GeoJSON sample data files. These files are good examples of the types of vector data that you can upload to Kibana and index in -Elasticsearch for display in *Elastic Maps*. +Elasticsearch for display in *Elastic Maps*. * https://raw.githubusercontent.com/elastic/examples/master/Maps/Getting%20Started%20Examples/geojson_upload_and_styling/logan_international_airport.geojson[Logan International Airport] * https://raw.githubusercontent.com/elastic/examples/master/Maps/Getting%20Started%20Examples/geojson_upload_and_styling/bangor_international_airport.geojson[Bangor International Airport] @@ -23,7 +23,7 @@ Elasticsearch for display in *Elastic Maps*. * https://raw.githubusercontent.com/elastic/examples/master/Maps/Getting%20Started%20Examples/geojson_upload_and_styling/original_flight_path.geojson[Original flight path] * https://raw.githubusercontent.com/elastic/examples/master/Maps/Getting%20Started%20Examples/geojson_upload_and_styling/modified_flight_path.geojson[Modified flight path] -The data represents two real airports, two fictitious flight routes, and +The data represents two real airports, two fictitious flight routes, and fictitious lightning reports. You don't need to use all of these files. Feel free to work with as many files as you'd like, or use valid GeoJSON files of your own. @@ -47,12 +47,12 @@ image::maps/images/fu_gs_new_england_map.png[] For each GeoJSON file you downloaded, complete the following steps: . Below the map legend, click *Add layer*. -. From the list of layer types, click *Upload GeoJSON vector file*. +. From the list of layer types, click *Uploaded GeoJSON*. . Using the File Picker, upload the GeoJSON file. + -Depending on the geometry type of your features, this will +Depending on the geometry type of your features, this will auto-populate *Index type* with either {ref}/geo-point.html[geo_point] or - {ref}/geo-shape.html[geo_shape] and *Index name* with + {ref}/geo-shape.html[geo_shape] and *Index name* with ``. . Click *Import file* in the lower right. @@ -60,7 +60,7 @@ auto-populate *Index type* with either {ref}/geo-point.html[geo_point] or You'll see activity as the GeoJSON Upload utility creates a new index and index pattern for the data set. When the process is complete, you should receive messages that the creation of the new index and index pattern -were successful. +were successful. . Click *Add layer* in the bottom right. @@ -69,7 +69,7 @@ were successful. . Once you've added all of the sample files, <>. + -At this point, you could consider the map complete, +At this point, you could consider the map complete, but there are a few additions and tweaks that you can make to tell a better story with your data. + @@ -80,18 +80,18 @@ image::maps/images/fu_gs_flight_paths.png[] === Add a heatmap aggregation layer Looking at the `Lightning detected` layer, it's clear where lightning has -struck. What's less clear, is if there have been more lightning -strikes in some areas than others, in other words, where the lightning +struck. What's less clear, is if there have been more lightning +strikes in some areas than others, in other words, where the lightning hot spots are. An advantage of having indexed -{ref}/geo-point.html[geo_point] data for the -lightning strikes is that you can perform aggregations on the data. +{ref}/geo-point.html[geo_point] data for the +lightning strikes is that you can perform aggregations on the data. . Below the map legend, click *Add layer*. . From the list of layer types, click *Grid aggregation*. + -Because you indexed `lightning_detected.geojson` using the index name and +Because you indexed `lightning_detected.geojson` using the index name and pattern `lightning_detected`, that data is available as a {ref}/geo-point.html[geo_point] -aggregation. +aggregation. . Select `lightning_detected`. . Click *Show as* and select `heat map`. @@ -99,15 +99,15 @@ aggregation. "Lightning intensity". + The remaining default settings are good, but there are a couple of -settings that you might want to change. +settings that you might want to change. -. Under *Source settings* > *Grid resolution*, select from the different heat map resolutions. +. Under *Source settings* > *Grid resolution*, select from the different heat map resolutions. + The default "Coarse" looks good, but feel free to select a different resolution. . Play around with the *Layer Style* > -*Color range* setting. +*Color range* setting. + Again the default looks good, but feel free to choose a different color range. @@ -125,14 +125,14 @@ image::maps/images/fu_gs_lightning_intensity.png[] === Organize the layers Consider ways you might improve the appearance of the final map. -Small changes in how and when layers are shown can help tell a +Small changes in how and when layers are shown can help tell a better story with your data. Here are a few final tweaks you might make: * Update layer names * Adjust styles for each layer * Adjust the layer order -* Decide which layers to show at different zoom levels +* Decide which layers to show at different zoom levels When you've finished, again be sure to <>. diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index f6db2f0fff219c..88ad6a26d36970 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -65,7 +65,7 @@ and lighter shades symbolize countries with less traffic. ==== Add a vector layer from the Elastic Maps Service source . In the map legend, click *Add layer*. -. Click the *Vector shapes* data source. +. Click the *EMS Boundaries* data source. . From the *Layer* dropdown menu, select *World Countries*. . Click the *Add layer* button. . Set *Layer name* to `Total Requests by Country`. diff --git a/docs/maps/trouble-shooting.asciidoc b/docs/maps/trouble-shooting.asciidoc index 542138828530bb..76383f8953a781 100644 --- a/docs/maps/trouble-shooting.asciidoc +++ b/docs/maps/trouble-shooting.asciidoc @@ -30,4 +30,9 @@ image::maps/images/inspector.png[] * Ensure your tile server has configured https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[Cross-Origin Resource Sharing (CORS)] so tile requests from your Kibana domain have permission to access your tile server domain. * Ensure tiles have the required coordinate system. Vector data must use EPSG:4326 and tiles must use EPSG:3857. +[float] +==== Coordinate and region map visualizations not available in New Visualization menu + +Kibana’s out-of-the-box settings no longer offers coordinate and region maps as a choice in the New Visualization menu because you can create these maps in *Elastic Maps*. +If you want to create new coordinate and region map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index a8d8a66fd182c7..af67ff70c81b59 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -35,3 +35,7 @@ This page has moved. Please see <>. This page has moved. Please see <>. +[role="exclude",id="extend"] +== Extend your use case + +This page was deleted. See <> and <>. \ No newline at end of file diff --git a/docs/settings/code-settings.asciidoc b/docs/settings/code-settings.asciidoc deleted file mode 100644 index 5dbcb9b3508b8f..00000000000000 --- a/docs/settings/code-settings.asciidoc +++ /dev/null @@ -1,51 +0,0 @@ -[role="xpack"] -[[code-settings-kibana]] -=== Code Settings in Kibana -++++ -Code settings -++++ - -Unless you are running multiple Kibana instances as a cluster, you do not need to change any settings to use *Code* by default. If you’d like to change any of the default values, copy and paste the relevant settings below into your `kibana.yml` configuration file. - - -`xpack.code.updateRepoFrequencyMs`:: -Repo update frequency in milliseconds. Defaults to `300000`. - -`xpack.code.indexRepoFrequencyMs`:: -Repo index frequency in milliseconds. Defaults to `86400000`. - -`xpack.code.lsp.verbose`:: -Whether to show more verbose log for language servers. Defaults to `false`. - -`xpack.code.security.enableMavenImport`:: -Whether to support maven. Defaults to `true`. - -`xpack.code.security.enableGradleImport`:: -Whether to support gradle. Defaults to `false`. - -`xpack.code.security.installNodeDependency`:: -Whether to enable node dependency download. Defaults to `true`. - -`xpack.code.security.gitHostWhitelist`:: -Whitelist of hostnames for git clone address. Defaults to `[ 'github.com', 'gitlab.com', 'bitbucket.org', 'gitbox.apache.org', 'eclipse.org',]`. - -`xpack.code.security.gitProtocolWhitelist`:: -Whitelist of protocols for git clone address. Defaults to `[ 'https', 'git', 'ssh' ]`. - -`xpack.code.security.enableGitCertCheck`:: -Whether enable HTTPS certificate check when clone from HTTPS URL. - -`xpack.code.security.enableJavaSecurityManager`:: -Whether enable Java security manager for Java langserver. Defaults to `true`. - -`xpack.code.security.extraJavaRepositoryWhitelist`:: -Whitelist of extra repository to download dependencies for Java language. Defaults to `[]`. - -`xpack.code.maxWorkspace`:: -Maximal number of workspaces each language server allows to span. Defaults to `5`. - -`xpack.code.codeNodeUrl`:: -URL of the Code node. This config is only needed when multiple Kibana instances are set up as a cluster. Defaults to `` - -`xpack.code.verbose`:: -Set this config to `true` to log all events. Defaults to `false` diff --git a/docs/settings/general-infra-logs-ui-settings.asciidoc b/docs/settings/general-infra-logs-ui-settings.asciidoc index 31a12c6e2e905c..7b32372a1f59a1 100644 --- a/docs/settings/general-infra-logs-ui-settings.asciidoc +++ b/docs/settings/general-infra-logs-ui-settings.asciidoc @@ -1,4 +1,4 @@ -`xpack.infra.enabled`:: Set to `false` to disable the Logs and Metrics UI plugin {kib}. Defaults to `true`. +`xpack.infra.enabled`:: Set to `false` to disable the Logs and Metrics app plugin {kib}. Defaults to `true`. `xpack.infra.sources.default.logAlias`:: Index pattern for matching indices that contain log data. Defaults to `filebeat-*,kibana_sample_data_logs*`. To match multiple wildcard patterns, use a comma to separate the names, with no space after the comma. For example, `logstash-app1-*,default-logs-*`. @@ -6,7 +6,7 @@ `xpack.infra.sources.default.fields.timestamp`:: Timestamp used to sort log entries. Defaults to `@timestamp`. -`xpack.infra.sources.default.fields.message`:: Fields used to display messages in the Logs UI. Defaults to `['message', '@message']`. +`xpack.infra.sources.default.fields.message`:: Fields used to display messages in the Logs app. Defaults to `['message', '@message']`. `xpack.infra.sources.default.fields.tiebreaker`:: Field used to break ties between two entries with the same timestamp. Defaults to `_doc`. diff --git a/docs/settings/infrastructure-ui-settings.asciidoc b/docs/settings/infrastructure-ui-settings.asciidoc index 1617f2847c50ed..ed69c27feab72a 100644 --- a/docs/settings/infrastructure-ui-settings.asciidoc +++ b/docs/settings/infrastructure-ui-settings.asciidoc @@ -1,14 +1,14 @@ [role="xpack"] [[infrastructure-ui-settings-kb]] -=== Metrics UI settings in Kibana +=== Metrics settings in Kibana ++++ -Metrics UI settings +Metrics settings ++++ -You do not need to configure any settings to use the Metrics UI. It is enabled by default. +You do not need to configure any settings to use the Metrics app in {kib}. It is enabled by default. [float] [[general-infra-ui-settings-kb]] -==== General Metrics UI settings +==== General Metrics settings include::general-infra-logs-ui-settings.asciidoc[] \ No newline at end of file diff --git a/docs/settings/logs-ui-settings.asciidoc b/docs/settings/logs-ui-settings.asciidoc index a2c9e12e22fb06..5b6dd902091ae5 100644 --- a/docs/settings/logs-ui-settings.asciidoc +++ b/docs/settings/logs-ui-settings.asciidoc @@ -1,14 +1,14 @@ [role="xpack"] [[logs-ui-settings-kb]] -=== Logs UI settings in Kibana +=== Logs app settings in Kibana ++++ -Logs UI settings +Logs settings ++++ -You do not need to configure any settings to use the Logs UI. It is enabled by default. +You do not need to configure any settings to use the Logs app in {kib}. It is enabled by default. [float] [[general-logs-ui-settings-kb]] -==== General Logs UI settings +==== General Logs settings include::general-infra-logs-ui-settings.asciidoc[] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index f02cf188d31ada..6e7f939a1d2abd 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -277,8 +277,8 @@ setting specifies the port to use. `server.rewriteBasePath:`:: *Default: deprecated* Specifies whether Kibana should rewrite requests that are prefixed with `server.basePath` or require that they -are rewritten by your reverse proxy. In Kibana 6.3 and earlier, the default is -`false`. In Kibana 7.x, the setting is deprecated. In Kibana 8.0 and later, the +are rewritten by your reverse proxy. In Kibana 6.3 and earlier, the default is +`false`. In Kibana 7.x, the setting is deprecated. In Kibana 8.0 and later, the default is `true`. `server.socketTimeout:`:: *Default: "120000"* The number of milliseconds to wait before closing an @@ -327,7 +327,6 @@ Rollup user interface. include::{docdir}/settings/apm-settings.asciidoc[] -include::{docdir}/settings/code-settings.asciidoc[] include::{docdir}/settings/dev-settings.asciidoc[] include::{docdir}/settings/graph-settings.asciidoc[] include::{docdir}/settings/infrastructure-ui-settings.asciidoc[] diff --git a/docs/uptime-guide/overview.asciidoc b/docs/uptime-guide/overview.asciidoc index 09867e7b05f2a5..c6bd71b1f55742 100644 --- a/docs/uptime-guide/overview.asciidoc +++ b/docs/uptime-guide/overview.asciidoc @@ -33,8 +33,8 @@ The {kibana-ref}/xpack-uptime.html[Elasticsearch Uptime app] in Kibana provides [float] === Example deployments -// ++ I like the Infra/logging diagram which shows Infrastructure and Logging apps as separate components inside Kibana -// ++ In diagram, should be Uptime app, not Uptime UI, possibly even Elastic Uptime? Also applies to Infra/logging/APM. +// ++ I like the Infra/logging diagram which shows Metrics and Logging apps as separate components inside Kibana +// ++ In diagram, should be Uptime app, not Uptime UI, possibly even Elastic Uptime? Also applies to Metrics/Logging/APM. // ++ Need more whitespace around components. image::images/uptime-simple-deployment.png[Uptime simple deployment] diff --git a/docs/uptime-guide/security.asciidoc b/docs/uptime-guide/security.asciidoc index 41efcc25627bee..2a960348b1e02c 100644 --- a/docs/uptime-guide/security.asciidoc +++ b/docs/uptime-guide/security.asciidoc @@ -31,17 +31,6 @@ PUT /_security/role/uptime "allow_restricted_indices" : false } ], - "applications" : [ - { - "application" : "kibana-.kibana", - "privileges" : [ - "all" - ], - "resources" : [ - "*" - ] - } - ], "transient_metadata" : { "enabled" : true } @@ -52,7 +41,8 @@ PUT /_security/role/uptime [float] === Assign the role to a user -Next, you'll need to create a user with both the `kibana_user`, and `uptime` roles. +Next, you'll need to create a user with both the `uptime` role, and another role with sufficient {kibana-ref}/kibana-privileges.html[Kibana privileges], +such as the `kibana_user` role. You can do this with the following request: ["source","sh",subs="attributes,callouts"] diff --git a/docs/uptime/overview.asciidoc b/docs/uptime/overview.asciidoc index ea7047ae940a8f..098ce12a569911 100644 --- a/docs/uptime/overview.asciidoc +++ b/docs/uptime/overview.asciidoc @@ -34,7 +34,7 @@ in an `up` or `down` state are displayed, based on the last check reported by He for each monitor. Next to the counts, there is a histogram displaying the change over time throughout the -selected date range. +selected date range. [float] === Monitor list @@ -56,7 +56,7 @@ ID and URL, its IP address, and a dedicated sparkline showing its check status o image::uptime/images/observability_integrations.png[Observability integrations] The Monitor list also contains a menu of possible integrations. If Uptime detects Kubernetes or -Docker related host information, it will provide links to open the Metrics UI or Logs UI pre-filtered +Docker related host information, it will provide links to open the Metrics app or Logs app pre-filtered for this host. Additionally, this feature supplies links to simply filter the other views on the host's IP address, to help you quickly determine if these other solutions contain data relevant to your current interest. diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index f3aa78e8e2e670..c58635ba977696 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -5,15 +5,15 @@ [partintro] -- -Canvas is a data visualization and presentation tool that sits within Kibana. With Canvas, you can pull live data directly from Elasticsearch, and combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then Canvas is for you. +Canvas is a data visualization and presentation tool that sits within Kibana. With Canvas, you can pull live data directly from Elasticsearch, and combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then Canvas is for you. With Canvas, you can: -* Create and personalize your work space with backgrounds, borders, colors, fonts, and more. +* Create and personalize your work space with backgrounds, borders, colors, fonts, and more. * Customize your workpad with your own visualizations, such as images and text. -* Customize your data by pulling it directly from Elasticsearch. +* Customize your data by pulling it directly from Elasticsearch. * Show off your data with charts, graphs, progress monitors, and more. @@ -37,8 +37,6 @@ include::{kib-repo-dir}/canvas/canvas-present-workpad.asciidoc[] include::{kib-repo-dir}/canvas/canvas-share-workpad.asciidoc[] -//include::{kib-repo-dir}/canvas/canvas-expressions.asciidoc[] - include::{kib-repo-dir}/canvas/canvas-function-reference.asciidoc[] include::{kib-repo-dir}/canvas/canvas-tinymath-functions.asciidoc[] diff --git a/docs/user/extend.asciidoc b/docs/user/extend.asciidoc deleted file mode 100644 index c658731ce3c3db..00000000000000 --- a/docs/user/extend.asciidoc +++ /dev/null @@ -1,15 +0,0 @@ -[[extend]] -= Extend your use case - -[partintro] --- -//TBD - -* <> -* <> - --- - -include::graph/index.asciidoc[] - -include::ml/index.asciidoc[] diff --git a/docs/user/graph/configuring-graph.asciidoc b/docs/user/graph/configuring-graph.asciidoc index 9503e606406b35..d521f9d8d2846d 100644 --- a/docs/user/graph/configuring-graph.asciidoc +++ b/docs/user/graph/configuring-graph.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[graph-configuration]] -=== Configuring Graph +== Configuring Graph When a user saves a graph workspace in Kibana, it is stored in the `.kibana` index along with other saved objects like visualizations and dashboards. @@ -57,9 +57,9 @@ is displayed. For more information on granting access to Kibana, see [role="screenshot"] image::user/graph/images/graph-read-only-badge.png[Example of Graph's read only access indicator in Kibana's header] -[float] +[discrete] [[disable-drill-down]] -==== Disabling drill down configuration +=== Disabling drill down configuration By default, users can configure _drill down_ URLs to display additional information about a selected vertex in a new browser window. For example, diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index dd5e8527c8976f..19f3df341338ec 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[graph-getting-started]] -=== Using Graph +== Using Graph Graph is automatically enabled in {es} and {kib}. diff --git a/docs/user/graph/index.asciidoc b/docs/user/graph/index.asciidoc index 9ca7b0e4b1a4a4..f9094f5b594b10 100644 --- a/docs/user/graph/index.asciidoc +++ b/docs/user/graph/index.asciidoc @@ -1,7 +1,9 @@ [role="xpack"] [[xpack-graph]] -== Graph data connections += Graph data connections +[partintro] +-- The {graph-features} enable you to discover how items in an Elasticsearch index are related. You can explore the connections between indexed terms and see which connections are the most meaningful. This can be @@ -17,9 +19,9 @@ and an interactive graph visualization tool for Kibana. Both work out of the box with existing Elasticsearch indices--you don't need to store any additional data to use these features. +[discrete] [[how-graph-works]] -[float] -=== How Graph works +== How Graph works The graph API provides an alternative way to extract and summarize information about the documents and terms in your Elasticsearch index. A _graph_ is really just a network of related items. In our case, this means a network of related @@ -62,6 +64,7 @@ multi-node clusters and scales with your Elasticsearch deployment. Advanced options let you control how your data is sampled and summarized. You can also set timeouts to prevent graph queries from adversely affecting the cluster. +-- include::getting-started.asciidoc[] diff --git a/docs/user/graph/limitations.asciidoc b/docs/user/graph/limitations.asciidoc index b40f15000483ad..e96910bd27b4c0 100644 --- a/docs/user/graph/limitations.asciidoc +++ b/docs/user/graph/limitations.asciidoc @@ -1,12 +1,12 @@ [role="xpack"] [[graph-limitations]] -=== Graph limitations +== Graph limitations ++++ Limitations ++++ -[float] -==== Limited support for multiple indices +[discrete] +=== Limited support for multiple indices The graph API can explore multiple indices, types, or aliases in a single API request, but the assumption is that each "hop" it performs is querying the same set of indices. Currently, it is not possible to diff --git a/docs/user/graph/troubleshooting.asciidoc b/docs/user/graph/troubleshooting.asciidoc index 7a87aba7b7f813..ff3568ed41afa2 100644 --- a/docs/user/graph/troubleshooting.asciidoc +++ b/docs/user/graph/troubleshooting.asciidoc @@ -1,12 +1,12 @@ [role="xpack"] [[graph-troubleshooting]] -=== Graph Troubleshooting +== Graph Troubleshooting ++++ Troubleshooting ++++ -[float] -==== Why are results missing? +[discrete] +=== Why are results missing? The default settings in Graph API requests are configured to tune out noisy results by using the following strategies: @@ -29,8 +29,8 @@ of any statistical correlation with the sample. * Set the `min_doc_count` for your vertices to 1 to ensure only one document is required to assert a relationship. -[float] -==== What can I do to to improve performance? +[discrete] +=== What can I do to to improve performance? With the default setting of `use_significance` set to `true`, the Graph API performs a background frequency check of the terms it discovers as part of diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index 54817a6e8743d0..ebe6c10c498724 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -16,11 +16,11 @@ include::dashboard.asciidoc[] include::canvas.asciidoc[] -include::extend.asciidoc[] +include::graph/index.asciidoc[] -include::{kib-repo-dir}/maps/index.asciidoc[] +include::ml/index.asciidoc[] -include::{kib-repo-dir}/code/index.asciidoc[] +include::{kib-repo-dir}/maps/index.asciidoc[] include::{kib-repo-dir}/infrastructure/index.asciidoc[] diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc index f4802592f0e077..a2c23aad98d5be 100644 --- a/docs/user/ml/index.asciidoc +++ b/docs/user/ml/index.asciidoc @@ -1,7 +1,9 @@ [role="xpack"] [[xpack-ml]] -== {ml-cap} += {ml-cap} +[partintro] +-- As datasets increase in size and complexity, the human effort required to inspect dashboards or maintain rules for spotting infrastructure problems, cyber attacks, or business issues becomes impractical. Elastic {ml-features} @@ -18,11 +20,20 @@ image::user/ml/images/ml-data-visualizer-sample.jpg[Data Visualizer for sample f experimental[] You can also upload a CSV, NDJSON, or log file (up to 100 MB in size). The *Data Visualizer* identifies the file format and field mappings. You -can then optionally import that data into an {es} index. +can then optionally import that data into an {es} index. + +You need the following permissions to use the Data Visualizer with file upload: + +* cluster privileges: `monitor`, `manage_ingest_pipelines` +* index privileges: `read`, `manage`, `index` + +For more information, see {ref}/security-privileges.html[Security privileges] +and {ref}/built-in-roles.html[Built-in roles]. + +-- -[float] [[xpack-ml-anomalies]] -=== {anomaly-detect-cap} +== {anomaly-detect-cap} The Elastic {ml} {anomaly-detect} feature automatically models the normal behavior of your time series data — learning trends, periodicity, and more — in @@ -73,9 +84,8 @@ For more information about the {anomaly-detect} feature, see https://www.elastic.co/what-is/elastic-stack-machine-learning[{ml-cap} in the {stack}] and {stack-ov}/xpack-ml.html[{ml-cap} {anomaly-detect}]. -[float] [[xpack-ml-dfanalytics]] -=== {dfanalytics-cap} +== {dfanalytics-cap} The Elastic {ml} {dfanalytics} feature enables you to analyze your data using {oldetection} and {regression} algorithms and generate new indices that contain @@ -89,4 +99,4 @@ in {kib}. For example: image::user/ml/images/outliers.jpg[{oldetection-cap} results in {kib}] For more information about the {dfanalytics} feature, see -{stack-ov}/ml-dfanalytics.html[{ml-cap} {dfanalytics}]. +{stack-ov}/ml-dfanalytics.html[{ml-cap} {dfanalytics}]. \ No newline at end of file diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 82dd88cb57495a..4a5ca41ae6be97 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -32,8 +32,8 @@ for different operating systems. . Open {kib} in your web browser and log in. If you are running {kib} locally, go to `http://localhost:5601`. To access {kib} and generate -reports, you need the `kibana_user` and `reporting_user` roles. For more -information, see <>. +reports, you need the `reporting_user` role, and an additional role with succifient <>, such as the `kibana_user` role. +For more information, see <>. . Open the dashboard, visualization, or saved search you want to include in the report. diff --git a/docs/user/security/api-keys/images/api-key-invalidate.png b/docs/user/security/api-keys/images/api-key-invalidate.png new file mode 100755 index 00000000000000..c925679ab24bc6 Binary files /dev/null and b/docs/user/security/api-keys/images/api-key-invalidate.png differ diff --git a/docs/user/security/api-keys/images/api-keys.png b/docs/user/security/api-keys/images/api-keys.png new file mode 100755 index 00000000000000..df74f245676d9c Binary files /dev/null and b/docs/user/security/api-keys/images/api-keys.png differ diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc new file mode 100644 index 00000000000000..c00f58cf598e38 --- /dev/null +++ b/docs/user/security/api-keys/index.asciidoc @@ -0,0 +1,86 @@ +[role="xpack"] +[[api-keys]] +=== API Keys + + +API keys enable you to create secondary credentials so that you can send +requests on behalf of the user. Secondary credentials have +the same or lower access rights. + +For example, if you extract data from an {es} cluster on a daily +basis, you might create an API key tied to your credentials, +configure it with minimum access, +and then put the API credentials into a cron job. +Or, you might create API keys to automate ingestion of new data from +remote sources, without a live user interaction. + +You can create API keys from the {kib} Console. To view and invalidate +API keys, use *Management > Security > API Keys*. + +[role="screenshot"] +image:user/security/api-keys/images/api-keys.png["API Keys UI"] + +[float] +[[api-keys-service]] +=== {es} API key service + +The {es} API key service is automatically enabled when you configure +{ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. +This ensures that clients are unable to send API keys in clear-text. + +When HTTPS connections are not enabled between {kib} and {es}, +you cannot create or manage API keys, and you get an error message. +For more information, see the +{ref}/security-api-create-api-key.html[{es} API key documentation], +or contact your system administrator. + +[float] +[[api-keys-security-privileges]] +=== Security privileges + +You must have the `manage_security`, `manage_api_key`, or the `manage_own_api_key` +cluster privileges to use API keys in {kib}. You can manage roles in +*Management > Security > Roles*, or use the <>. + + +[float] +[[create-api-key]] +=== Create an API key +You can {ref}/security-api-create-api-key.html[create an API key] from +the Kibana Console. For example: + +[source,js] +POST /_security/api_key +{ + "name": "my_api_key", + "expiration": "1d" +} + +This creates an API key with the name `my_api_key` that +expires after one day. API key names must be globally unique. +An expiration date is optional and follows {ref}/common-options.html#time-units[{es} time unit format]. +When an expiration is not provided, the API key does not expire. + +[float] +[[view-api-keys]] +=== View and invalidate API keys +The *API Keys* UI lists your API keys, including the name, date created, +and expiration date. If an API key expires, its status changes from `Active` to `Expired`. + +If you have `manage_security` or `manage_api_key` permissions, +you can view the API keys of all users, and see which API key was +created by which user in which realm. +If you have only the `manage_own_api_key` permission, you see only a list of your own keys. + +You can invalidate API keys individually or in bulk. +Invalidated keys are deleted in batch after seven days. + +[role="screenshot"] +image:user/security/api-keys/images/api-key-invalidate.png["API Keys invalidate"] + +You cannot modify an API key. If you need additional privileges, +you must create a new key with the desired configuration and invalidate the old key. + + + + diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 8a4678e051490b..c2b1adc5e1b921 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -138,7 +138,7 @@ xpack.security.authc.saml.maxRedirectURLSize: 1kb ==== OpenID Connect Single Sign-On Similar to SAML, authentication with OpenID Connect allows users to log in to {kib} using an OpenID Connect Provider such as Google, or Okta. OpenID Connect -should also be configured in {es}, see {xpack-ref}/saml-guide.html[Configuring OpenID Connect Single-Sign-On on the Elastic Stack] for more details. +should also be configured in {es}. For more details, see {ref}/oidc-guide.html[Configuring single sign-on to the {stack} using OpenID Connect]. Set the configuration values in `kibana.yml` as follows: diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index 7b7e38d6108430..f57d1bcd3bc2aa 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -36,3 +36,5 @@ cause Kibana's authorization to behave unexpectedly. include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] +include::api-keys/index.asciidoc[] + diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index ffbef3bdc9b189..1d7a3f4978ee09 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -19,7 +19,7 @@ and `kibana_user` roles: * If you're using the `native` realm, you can assign roles through **Management / Users** UI in Kibana or with the `user` API. For example, the following request creates a `reporter` user that has the -`reporting_user` and `kibana_user` roles: +`reporting_user` role, and another role with sufficient <>, such as the `kibana_user` role: + [source, sh] --------------------------------------------------------------- diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index fa11d5925bdbeb..1c74bd98642a7c 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -117,7 +117,7 @@ user you've assigned a {kib} user role. For example, you could log in as the + -- -NOTE: This must be a user who has been assigned the `kibana_user` role. +NOTE: This must be a user who has been assigned <>. {kib} server credentials should only be used internally by the {kib} server. -- diff --git a/docs/visualize/regionmap.asciidoc b/docs/visualize/regionmap.asciidoc index faecc207d81a70..c39282963ef7bc 100644 --- a/docs/visualize/regionmap.asciidoc +++ b/docs/visualize/regionmap.asciidoc @@ -1,9 +1,12 @@ [[regionmap]] == Region Maps -Region maps are thematic maps in which boundary vector shapes are colored using a gradient: -higher intensity colors indicate larger values, and lower intensity colors indicate smaller values. -These are also known as choropleth maps. +Region maps are thematic maps in which boundary vector shapes are colored using a gradient: +higher intensity colors indicate larger values, and lower intensity colors indicate smaller values. +These are also known as choropleth maps. + +Kibana’s out-of-the-box settings do not show a region map in the New Visualization menu. Use <> instead, which offers more functionality and is easier to use. +If you want to create new region map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. image::images/regionmap.png[] @@ -11,7 +14,7 @@ image::images/regionmap.png[] [[regionmap-configuration]] === Configuration -To create a region map, you configure an inner join that joins the result of an Elasticsearch terms aggregation +To create a region map, you configure an inner join that joins the result of an Elasticsearch terms aggregation and a reference vector file based on a shared key. [float] @@ -23,7 +26,7 @@ and a reference vector file based on a shared key. Select any of the supported _Metric_ or _Sibling Pipeline Aggregations_. [float] -===== Buckets +===== Buckets Configure a _Terms_ aggregation. The term is the _key_ that is used to join the results to the vector data on the map. @@ -35,7 +38,7 @@ Configure a _Terms_ aggregation. The term is the _key_ that is used to join the - *Vector map*: select from a list of vector maps. This list includes the maps that are hosted by the © https://www.elastic.co/elastic-maps-service[Elastic Maps Service], as well as your self-hosted layers that are configured in the *config/kibana.yml* file. To learn more about how to configure Kibana to make self-hosted layers available, see the <> documentation. You can also explore and preview vector layers available in Elastic Maps Service at https://maps.elastic.co[https://maps.elastic.co]. -- *Join field*: this is the property from the selected vector map that will be used to join on the terms in your terms-aggregation. +- *Join field*: this is the property from the selected vector map that will be used to join on the terms in your terms-aggregation. When terms cannot be joined to any of the shapes in the vector layer because there is no exact match in the vector layer, Kibana will display a warning. To turn of these warnings, go to *Management/Kibana/Advanced Settings* and set `visualization:regionmap:showWarnings` to `false`. @@ -46,4 +49,4 @@ To turn of these warnings, go to *Management/Kibana/Advanced Settings* and set ` [float] ===== Basic Settings - *Legend Position*: the location on the screen where the legend should be rendered. -- *Show Tooltip*: indicates whether a tooltip should be displayed when hovering over a shape.. \ No newline at end of file +- *Show Tooltip*: indicates whether a tooltip should be displayed when hovering over a shape.. diff --git a/docs/visualize/tilemap.asciidoc b/docs/visualize/tilemap.asciidoc index 157eb502bedb7d..0e89704b8ba0b2 100644 --- a/docs/visualize/tilemap.asciidoc +++ b/docs/visualize/tilemap.asciidoc @@ -3,7 +3,10 @@ A coordinate map displays a geographic area overlaid with circles keyed to the data determined by the buckets you specify. -NOTE: By default, Kibana uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service] +Kibana’s out-of-the-box settings do not show a coordinate map in the New Visualization menu. Use <> instead, which offers more functionality and is easier to use. +If you want to create new coordinate map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. + +Kibana uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service] to display map tiles. To use other tile service providers, configure the <> in `kibana.yml`. diff --git a/package.json b/package.json index ffd0e9387809db..a8e60e9749f724 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "**/typescript": "3.5.3", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", - "**/babel-plugin-inline-react-svg/svgo/js-yaml": "^3.13.1" + "**/image-diff/gm/debug": "^2.6.9" }, "workspaces": { "packages": [ @@ -106,10 +106,10 @@ "dependencies": { "@babel/core": "^7.5.5", "@babel/register": "^7.5.5", - "@elastic/charts": "^13.5.4", + "@elastic/charts": "^13.5.12", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.8.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -155,11 +155,11 @@ "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", - "del": "^4.1.1", - "elasticsearch": "^16.4.0", - "elasticsearch-browser": "^16.4.0", + "del": "^5.1.0", + "elasticsearch": "^16.5.0", + "elasticsearch-browser": "^16.5.0", "encode-uri-query": "1.0.1", - "execa": "^1.0.0", + "execa": "^3.2.0", "expiry-js": "0.1.7", "file-loader": "4.2.0", "font-awesome": "4.7.0", @@ -198,8 +198,8 @@ "markdown-it": "^8.4.1", "mini-css-extract-plugin": "0.8.0", "minimatch": "^3.0.4", - "moment": "^2.20.1", - "moment-timezone": "^0.5.14", + "moment": "^2.24.0", + "moment-timezone": "^0.5.27", "mustache": "2.3.2", "ngreact": "0.5.1", "node-fetch": "1.7.3", @@ -232,12 +232,10 @@ "request": "^2.88.0", "reselect": "^3.0.1", "resize-observer-polyfill": "^1.5.0", - "rimraf": "2.7.1", "rison-node": "1.0.2", - "rxjs": "^6.2.1", + "rxjs": "^6.5.3", "script-loader": "0.7.2", "semver": "^5.5.0", - "stream-stream": "^1.2.6", "style-it": "^2.1.3", "style-loader": "0.23.1", "symbol-observable": "^1.2.0", @@ -247,8 +245,6 @@ "tinygradient": "0.4.3", "tinymath": "1.2.1", "topojson-client": "3.0.0", - "trunc-html": "1.1.2", - "trunc-text": "1.0.2", "tslib": "^1.9.3", "type-detect": "^4.0.8", "ui-select": "0.19.8", @@ -275,7 +271,7 @@ "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", - "@elastic/makelogs": "^4.5.0", + "@elastic/makelogs": "^5.0.0", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", "@kbn/eslint-import-resolver-kibana": "2.0.0", @@ -302,7 +298,6 @@ "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.9.0", "@types/eslint": "^6.1.2", - "@types/execa": "^0.9.0", "@types/fetch-mock": "^7.3.1", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.1", @@ -313,7 +308,6 @@ "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", - "@types/humps": "^1.1.2", "@types/jest": "^24.0.18", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", @@ -326,7 +320,7 @@ "@types/markdown-it": "^0.0.7", "@types/minimatch": "^2.0.29", "@types/mocha": "^5.2.7", - "@types/moment-timezone": "^0.5.8", + "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", "@types/opn": "^5.1.0", @@ -341,13 +335,13 @@ "@types/redux": "^3.6.31", "@types/redux-actions": "^2.2.1", "@types/request": "^2.48.2", - "@types/rimraf": "^2.0.2", "@types/selenium-webdriver": "^4.0.3", "@types/semver": "^5.5.0", "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", "@types/styled-components": "^3.0.2", "@types/supertest": "^2.0.5", + "@types/supertest-as-promised": "^2.0.38", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", @@ -356,6 +350,7 @@ "@typescript-eslint/parser": "^2.5.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", + "axe-core": "^3.3.2", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", "babel-plugin-dynamic-import-node": "^2.3.0", @@ -365,7 +360,7 @@ "chance": "1.0.18", "cheerio": "0.22.0", "chokidar": "3.2.1", - "chromedriver": "^77.0.0", + "chromedriver": "^78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", "delete-empty": "^2.0.0", diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index 53c8459821147a..57873d28d372de 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -14,12 +14,12 @@ "@babel/cli": "^7.5.5", "@babel/preset-env": "^7.5.5", "babel-plugin-add-module-exports": "^1.0.2", - "moment": "^2.13.0" + "moment": "^2.24.0" }, "dependencies": { "tslib": "^1.9.3" }, "peerDependencies": { - "moment": "^2.13.0" + "moment": "^2.24.0" } } diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 4412ce81368a1b..36f0b95c8e69b6 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { './javascript.js', './typescript.js', './jest.js', + './react.js', ], plugins: ['@kbn/eslint-plugin-eslint'], diff --git a/packages/eslint-config-kibana/javascript.js b/packages/eslint-config-kibana/javascript.js index 8462da5cac801c..0c79669c15e733 100644 --- a/packages/eslint-config-kibana/javascript.js +++ b/packages/eslint-config-kibana/javascript.js @@ -1,6 +1,3 @@ -const semver = require('semver'); -const { readdirSync } = require('fs'); -const PKG = require('../../package.json'); const RESTRICTED_GLOBALS = require('./restricted_globals'); const RESTRICTED_MODULES = { paths: ['gulp-util'] }; @@ -16,18 +13,12 @@ module.exports = { plugins: [ 'mocha', 'babel', - 'react', - 'react-hooks', 'import', 'no-unsanitized', 'prefer-object-spread', - 'jsx-a11y', ], settings: { - react: { - version: semver.valid(semver.coerce(PKG.dependencies.react)), - }, 'import/resolver': { '@kbn/eslint-import-resolver-kibana': { forceNode: true, @@ -120,64 +111,6 @@ module.exports = { 'object-curly-spacing': 'off', // overridden with babel/object-curly-spacing 'babel/object-curly-spacing': [ 'error', 'always' ], - 'jsx-quotes': ['error', 'prefer-double'], - 'react/jsx-uses-react': 'error', - 'react/react-in-jsx-scope': 'error', - 'react/jsx-uses-vars': 'error', - 'react/jsx-no-undef': 'error', - 'react/jsx-pascal-case': 'error', - 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], - 'react/jsx-closing-tag-location': 'error', - 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }], - 'react/jsx-indent-props': ['error', 2], - 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], - 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], - 'react/no-danger': 'error', - 'react/self-closing-comp': 'error', - 'react/jsx-wrap-multilines': ['error', { - declaration: true, - assignment: true, - return: true, - arrow: true, - }], - 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], - 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks - 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies - 'jsx-a11y/accessible-emoji': 'error', - 'jsx-a11y/alt-text': 'error', - 'jsx-a11y/anchor-has-content': 'error', - 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', - 'jsx-a11y/aria-props': 'error', - 'jsx-a11y/aria-proptypes': 'error', - 'jsx-a11y/aria-role': 'error', - 'jsx-a11y/aria-unsupported-elements': 'error', - 'jsx-a11y/heading-has-content': 'error', - 'jsx-a11y/html-has-lang': 'error', - 'jsx-a11y/iframe-has-title': 'error', - 'jsx-a11y/interactive-supports-focus': 'error', - 'jsx-a11y/media-has-caption': 'error', - 'jsx-a11y/mouse-events-have-key-events': 'error', - 'jsx-a11y/no-access-key': 'error', - 'jsx-a11y/no-distracting-elements': 'error', - 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', - 'jsx-a11y/no-noninteractive-element-interactions': 'error', - 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error', - 'jsx-a11y/no-redundant-roles': 'error', - 'jsx-a11y/role-has-required-aria-props': 'error', - 'jsx-a11y/role-supports-aria-props': 'error', - 'jsx-a11y/scope': 'error', - 'jsx-a11y/tabindex-no-positive': 'error', - 'jsx-a11y/label-has-associated-control': 'error', - 'react/jsx-equals-spacing': ['error', 'never'], - 'react/jsx-indent': ['error', 2], - 'react/no-will-update-set-state': 'error', - 'react/no-is-mounted': 'error', - 'react/no-multi-comp': ['error', { ignoreStateless: true }], - 'react/no-unknown-property': 'error', - 'react/prefer-es6-class': ['error', 'always'], - 'react/prefer-stateless-function': ['error', { ignorePureComponents: true }], - 'react/no-unescaped-entities': 'error', - 'mocha/handle-done-callback': 'error', 'mocha/no-exclusive-tests': 'error', diff --git a/packages/eslint-config-kibana/react.js b/packages/eslint-config-kibana/react.js new file mode 100644 index 00000000000000..163bd6ca73a07b --- /dev/null +++ b/packages/eslint-config-kibana/react.js @@ -0,0 +1,84 @@ +const semver = require('semver') +const PKG = require('../../package.json') + +module.exports = { + plugins: [ + 'react', + 'react-hooks', + 'jsx-a11y', + ], + + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + + settings: { + react: { + version: semver.valid(semver.coerce(PKG.dependencies.react)), + }, + }, + + rules: { + 'jsx-quotes': ['error', 'prefer-double'], + 'react/jsx-uses-react': 'error', + 'react/react-in-jsx-scope': 'error', + 'react/jsx-uses-vars': 'error', + 'react/jsx-no-undef': 'error', + 'react/jsx-pascal-case': 'error', + 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], + 'react/jsx-closing-tag-location': 'error', + 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }], + 'react/jsx-indent-props': ['error', 2], + 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], + 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], + 'react/no-danger': 'error', + 'react/self-closing-comp': 'error', + 'react/jsx-wrap-multilines': ['error', { + declaration: true, + assignment: true, + return: true, + arrow: true, + }], + 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], + 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks + 'react-hooks/exhaustive-deps': 'error', // Checks effect dependencies + 'jsx-a11y/accessible-emoji': 'error', + 'jsx-a11y/alt-text': 'error', + 'jsx-a11y/anchor-has-content': 'error', + 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', + 'jsx-a11y/aria-props': 'error', + 'jsx-a11y/aria-proptypes': 'error', + 'jsx-a11y/aria-role': 'error', + 'jsx-a11y/aria-unsupported-elements': 'error', + 'jsx-a11y/click-events-have-key-events': 'error', + 'jsx-a11y/heading-has-content': 'error', + 'jsx-a11y/html-has-lang': 'error', + 'jsx-a11y/iframe-has-title': 'error', + 'jsx-a11y/interactive-supports-focus': 'error', + 'jsx-a11y/label-has-associated-control': 'error', + 'jsx-a11y/media-has-caption': 'error', + 'jsx-a11y/mouse-events-have-key-events': 'error', + 'jsx-a11y/no-access-key': 'error', + 'jsx-a11y/no-distracting-elements': 'error', + 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', + 'jsx-a11y/no-noninteractive-element-interactions': 'error', + 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error', + 'jsx-a11y/no-onchange': 'error', + 'jsx-a11y/no-redundant-roles': 'error', + 'jsx-a11y/role-has-required-aria-props': 'error', + 'jsx-a11y/role-supports-aria-props': 'error', + 'jsx-a11y/scope': 'error', + 'jsx-a11y/tabindex-no-positive': 'error', + 'react/jsx-equals-spacing': ['error', 'never'], + 'react/jsx-indent': ['error', 2], + 'react/no-will-update-set-state': 'error', + 'react/no-is-mounted': 'error', + 'react/no-multi-comp': ['error', { ignoreStateless: true }], + 'react/no-unknown-property': 'error', + 'react/prefer-es6-class': ['error', 'always'], + 'react/prefer-stateless-function': ['error', { ignorePureComponents: true }], + 'react/no-unescaped-entities': 'error', + }, +} diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index 7653c9bb0c3323..757616f36180b6 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -18,7 +18,6 @@ module.exports = { '@typescript-eslint', 'ban', 'import', - 'jsx-a11y', 'prefer-object-spread', ], @@ -171,33 +170,6 @@ module.exports = { {'name': ['test', 'only'], 'message': 'No exclusive tests.'}, ], - 'jsx-a11y/accessible-emoji': 'error', - 'jsx-a11y/alt-text': 'error', - 'jsx-a11y/anchor-has-content': 'error', - 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', - 'jsx-a11y/aria-props': 'error', - 'jsx-a11y/aria-proptypes': 'error', - 'jsx-a11y/aria-role': 'error', - 'jsx-a11y/aria-unsupported-elements': 'error', - 'jsx-a11y/click-events-have-key-events': 'error', - 'jsx-a11y/heading-has-content': 'error', - 'jsx-a11y/html-has-lang': 'error', - 'jsx-a11y/iframe-has-title': 'error', - 'jsx-a11y/interactive-supports-focus': 'error', - 'jsx-a11y/media-has-caption': 'error', - 'jsx-a11y/mouse-events-have-key-events': 'error', - 'jsx-a11y/no-access-key': 'error', - 'jsx-a11y/no-distracting-elements': 'error', - 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', - 'jsx-a11y/no-noninteractive-element-interactions': 'error', - 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error', - 'jsx-a11y/no-onchange': 'error', - 'jsx-a11y/no-redundant-roles': 'error', - 'jsx-a11y/role-has-required-aria-props': 'error', - 'jsx-a11y/role-supports-aria-props': 'error', - 'jsx-a11y/scope': 'error', - 'jsx-a11y/tabindex-no-positive': 'error', - 'jsx-a11y/label-has-associated-control': 'error', 'import/no-default-export': 'error', }, eslintConfigPrettierTypescriptEslintRules diff --git a/packages/kbn-babel-preset/node_preset.js b/packages/kbn-babel-preset/node_preset.js index 5ef4219df59f75..793044e3796ea0 100644 --- a/packages/kbn-babel-preset/node_preset.js +++ b/packages/kbn-babel-preset/node_preset.js @@ -18,6 +18,25 @@ */ module.exports = (_, options = {}) => { + const overrides = []; + if (!process.env.ALLOW_PERFORMANCE_HOOKS_IN_TASK_MANAGER) { + overrides.push( + { + test: [/x-pack[\/\\]legacy[\/\\]plugins[\/\\]task_manager/], + plugins: [ + [ + require.resolve('babel-plugin-filter-imports'), + { + imports: { + perf_hooks: ['performance'], + }, + }, + ], + ], + } + ); + } + return { presets: [ [ @@ -39,7 +58,7 @@ module.exports = (_, options = {}) => { modules: 'cjs', corejs: 3, - ...(options['@babel/preset-env'] || {}) + ...(options['@babel/preset-env'] || {}), }, ], require('./common_preset'), @@ -48,9 +67,10 @@ module.exports = (_, options = {}) => { [ require.resolve('babel-plugin-transform-define'), { - 'global.__BUILT_WITH_BABEL__': 'true' - } - ] - ] + 'global.__BUILT_WITH_BABEL__': 'true', + }, + ], + ], + overrides, }; }; diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index 4b183577453606..c22cf175b29e59 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -8,10 +8,11 @@ "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-transform-modules-commonjs": "^7.5.0", "@babel/preset-env": "^7.5.5", - "@babel/preset-react":"^7.0.0", + "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", "@kbn/elastic-idx": "1.0.0", "babel-plugin-add-module-exports": "^1.0.2", + "babel-plugin-filter-imports": "^3.0.0", "babel-plugin-transform-define": "^1.3.1", "babel-plugin-typescript-strip-namespaces": "^1.1.1" } diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index cd5ae010f998b7..4880fb4ebfdeeb 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -14,7 +14,7 @@ }, "peerDependencies": { "joi": "^13.5.2", - "moment": "^2.20.1", + "moment": "^2.24.0", "type-detect": "^4.0.8" } } diff --git a/packages/kbn-config-schema/src/errors/validation_error.ts b/packages/kbn-config-schema/src/errors/validation_error.ts index 39aac67c208f57..d688d022da85cd 100644 --- a/packages/kbn-config-schema/src/errors/validation_error.ts +++ b/packages/kbn-config-schema/src/errors/validation_error.ts @@ -20,17 +20,18 @@ import { SchemaError, SchemaTypeError, SchemaTypesError } from '.'; export class ValidationError extends SchemaError { - public static extractMessage(error: SchemaTypeError, namespace?: string) { + private static extractMessage(error: SchemaTypeError, namespace?: string, level?: number) { const path = typeof namespace === 'string' ? [namespace, ...error.path] : error.path; let message = error.message; if (error instanceof SchemaTypesError) { + const indentLevel = level || 0; const childErrorMessages = error.errors.map(childError => - ValidationError.extractMessage(childError, namespace) + ValidationError.extractMessage(childError, namespace, indentLevel + 1) ); message = `${message}\n${childErrorMessages - .map(childErrorMessage => `- ${childErrorMessage}`) + .map(childErrorMessage => `${' '.repeat(indentLevel)}- ${childErrorMessage}`) .join('\n')}`; } diff --git a/packages/kbn-config-schema/src/types/map_of_type.test.ts b/packages/kbn-config-schema/src/types/map_of_type.test.ts index 1b72d39fcec263..6b9b700efdc3c3 100644 --- a/packages/kbn-config-schema/src/types/map_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/map_of_type.test.ts @@ -108,3 +108,23 @@ test('object within mapOf', () => { expect(type.validate(value)).toEqual(expected); }); + +test('error preserves full path', () => { + const type = schema.object({ + grandParentKey: schema.object({ + parentKey: schema.mapOf(schema.string({ minLength: 2 }), schema.number()), + }), + }); + + expect(() => + type.validate({ grandParentKey: { parentKey: { a: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.key(\\"a\\")]: value is [a] but it must have a minimum length of [2]."` + ); + + expect(() => + type.validate({ grandParentKey: { parentKey: { ab: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.ab]: expected value of type [number] but got [string]"` + ); +}); diff --git a/packages/kbn-config-schema/src/types/map_type.ts b/packages/kbn-config-schema/src/types/map_type.ts index 3acf14a69125e1..c637eccb795715 100644 --- a/packages/kbn-config-schema/src/types/map_type.ts +++ b/packages/kbn-config-schema/src/types/map_type.ts @@ -50,7 +50,7 @@ export class MapOfType extends Type> { return `expected value of type [Map] or [object] but got [${typeDetect(value)}]`; case 'map.key': case 'map.value': - const childPathWithIndex = reason.path.slice(); + const childPathWithIndex = path.slice(); childPathWithIndex.splice( path.length, 0, diff --git a/packages/kbn-config-schema/src/types/one_of_type.test.ts b/packages/kbn-config-schema/src/types/one_of_type.test.ts index 72119e761590b1..c84ae49df7aefb 100644 --- a/packages/kbn-config-schema/src/types/one_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/one_of_type.test.ts @@ -125,3 +125,20 @@ test('fails if not matching literal', () => { expect(() => type.validate('bar')).toThrowErrorMatchingSnapshot(); }); + +test('fails if nested union type fail', () => { + const type = schema.oneOf([ + schema.oneOf([schema.boolean()]), + schema.oneOf([schema.oneOf([schema.object({}), schema.number()])]), + ]); + + expect(() => type.validate('aaa')).toThrowErrorMatchingInlineSnapshot(` +"types that failed validation: +- [0]: types that failed validation: + - [0]: expected value of type [boolean] but got [string] +- [1]: types that failed validation: + - [0]: types that failed validation: + - [0]: expected a plain object value, but found [string] instead. + - [1]: expected value of type [number] but got [string]" +`); +}); diff --git a/packages/kbn-config-schema/src/types/record_of_type.test.ts b/packages/kbn-config-schema/src/types/record_of_type.test.ts index 036e34213411fd..2172160e8d1810 100644 --- a/packages/kbn-config-schema/src/types/record_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/record_of_type.test.ts @@ -120,3 +120,23 @@ test('object within recordOf', () => { expect(type.validate(value)).toEqual({ foo: { bar: 123 } }); }); + +test('error preserves full path', () => { + const type = schema.object({ + grandParentKey: schema.object({ + parentKey: schema.recordOf(schema.string({ minLength: 2 }), schema.number()), + }), + }); + + expect(() => + type.validate({ grandParentKey: { parentKey: { a: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.key(\\"a\\")]: value is [a] but it must have a minimum length of [2]."` + ); + + expect(() => + type.validate({ grandParentKey: { parentKey: { ab: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.ab]: expected value of type [number] but got [string]"` + ); +}); diff --git a/packages/kbn-config-schema/src/types/record_type.ts b/packages/kbn-config-schema/src/types/record_type.ts index cefb0a7921f4d4..82e585f685c569 100644 --- a/packages/kbn-config-schema/src/types/record_type.ts +++ b/packages/kbn-config-schema/src/types/record_type.ts @@ -42,7 +42,7 @@ export class RecordOfType extends Type> { return `expected value of type [object] but got [${typeDetect(value)}]`; case 'record.key': case 'record.value': - const childPathWithIndex = reason.path.slice(); + const childPathWithIndex = path.slice(); childPathWithIndex.splice( path.length, 0, diff --git a/packages/kbn-config-schema/src/types/union_type.ts b/packages/kbn-config-schema/src/types/union_type.ts index e6efb4afb66aa9..f4de829204e80b 100644 --- a/packages/kbn-config-schema/src/types/union_type.ts +++ b/packages/kbn-config-schema/src/types/union_type.ts @@ -41,7 +41,9 @@ export class UnionType>, T> extends Type { const childPathWithIndex = e.path.slice(); childPathWithIndex.splice(path.length, 0, index.toString()); - return new SchemaTypeError(e.message, childPathWithIndex); + return e instanceof SchemaTypesError + ? new SchemaTypesError(e.message, childPathWithIndex, e.errors) + : new SchemaTypeError(e.message, childPathWithIndex); }) ); } diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 5c64be294f7075..e8781f6d901d92 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -12,11 +12,11 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "execa": "^1.0.0", + "execa": "^3.2.0", "exit-hook": "^2.2.0", "getopts": "^2.2.5", - "moment": "^2.20.1", - "rxjs": "^6.2.1", + "moment": "^2.24.0", + "rxjs": "^6.5.3", "tree-kill": "^1.2.1", "tslib": "^1.9.3" }, diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts index f29fb5f4b17f6c..c899293191f2a7 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts @@ -87,6 +87,7 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { cwd, env, stdio: ['pipe', 'pipe', 'pipe'], + preferLocal: true, }); if (stdin) { @@ -99,9 +100,9 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { const outcome$: Rx.Observable = Rx.race( // observe first exit event - Rx.fromEvent(childProcess, 'exit').pipe( + Rx.fromEvent<[number]>(childProcess, 'exit').pipe( take(1), - map(([code]: [number]) => { + map(([code]) => { if (stopCalled) { return null; } diff --git a/packages/kbn-es-query/package.json b/packages/kbn-es-query/package.json index 5ef1b6e1254135..2cd2a8f53d2eed 100644 --- a/packages/kbn-es-query/package.json +++ b/packages/kbn-es-query/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "lodash": "npm:@elastic/lodash@3.10.1-kibana3", - "moment-timezone": "^0.5.14", + "moment-timezone": "^0.5.27", "@kbn/i18n": "1.0.0" }, "devDependencies": { @@ -21,7 +21,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@kbn/expect": "1.0.0", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "supports-color": "^7.0.0" } diff --git a/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json index 408e839596ae2f..588e6ada69cfe4 100644 --- a/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json +++ b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json @@ -161,8 +161,7 @@ "searchable": true, "aggregatable": true, "readFromDocValues": true, - "parent": "machine.os", - "subType": "multi" + "subType": { "multi": { "parent": "machine.os" } } }, { "name": "geo.src", @@ -277,6 +276,28 @@ "searchable": true, "aggregatable": true, "readFromDocValues": false + }, + { + "name": "nestedField.child", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField" } } + }, + { + "name": "nestedField.nestedChild.doublyNestedChild", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField.nestedChild" } } } ] } diff --git a/packages/kbn-es-query/src/es_query/migrate_filter.js b/packages/kbn-es-query/src/es_query/migrate_filter.js index d5f52648b027e2..b74fc485a6184f 100644 --- a/packages/kbn-es-query/src/es_query/migrate_filter.js +++ b/packages/kbn-es-query/src/es_query/migrate_filter.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getConvertedValueForField } from '../filters'; +import { getConvertedValueForField } from '../utils/filters'; export function migrateFilter(filter, indexPattern) { if (filter.match) { diff --git a/packages/kbn-es-query/src/filters/__tests__/phrase.js b/packages/kbn-es-query/src/filters/__tests__/phrase.js deleted file mode 100644 index dbd794a018d9ea..00000000000000 --- a/packages/kbn-es-query/src/filters/__tests__/phrase.js +++ /dev/null @@ -1,92 +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 { buildInlineScriptForPhraseFilter, buildPhraseFilter } from '../phrase'; -import expect from '@kbn/expect'; -import _ from 'lodash'; -import indexPattern from '../../__fixtures__/index_pattern_response.json'; -import filterSkeleton from '../../__fixtures__/filter_skeleton'; - -let expected; - -describe('Filter Manager', function () { - describe('Phrase filter builder', function () { - beforeEach(() => { - expected = _.cloneDeep(filterSkeleton); - }); - - it('should be a function', function () { - expect(buildPhraseFilter).to.be.a(Function); - }); - - it('should return a match query filter when passed a standard field', function () { - const field = getField(indexPattern, 'bytes'); - expected.query = { - match_phrase: { - bytes: 5 - } - }; - expect(buildPhraseFilter(field, 5, indexPattern)).to.eql(expected); - }); - - it('should return a script filter when passed a scripted field', function () { - const field = getField(indexPattern, 'script number'); - expected.meta.field = 'script number'; - _.set(expected, 'script.script', { - source: '(' + field.script + ') == value', - lang: 'expression', - params: { - value: 5, - } - }); - expect(buildPhraseFilter(field, 5, indexPattern)).to.eql(expected); - }); - }); - - describe('buildInlineScriptForPhraseFilter', function () { - - it('should wrap painless scripts in a lambda', function () { - const field = { - lang: 'painless', - script: 'return foo;', - }; - - const expected = `boolean compare(Supplier s, def v) {return s.get() == v;}` + - `compare(() -> { return foo; }, params.value);`; - - expect(buildInlineScriptForPhraseFilter(field)).to.be(expected); - }); - - it('should create a simple comparison for other langs', function () { - const field = { - lang: 'expression', - script: 'doc[bytes].value', - }; - - const expected = `(doc[bytes].value) == value`; - - expect(buildInlineScriptForPhraseFilter(field)).to.be(expected); - }); - }); -}); - -function getField(indexPattern, name) { - return indexPattern.fields.find(field => field.name === name); -} diff --git a/packages/kbn-es-query/src/filters/__tests__/query.js b/packages/kbn-es-query/src/filters/__tests__/query.js deleted file mode 100644 index 8b774f05c29d38..00000000000000 --- a/packages/kbn-es-query/src/filters/__tests__/query.js +++ /dev/null @@ -1,46 +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 { buildQueryFilter } from '../query'; -import { cloneDeep } from 'lodash'; -import expect from '@kbn/expect'; -import indexPattern from '../../__fixtures__/index_pattern_response.json'; -import filterSkeleton from '../../__fixtures__/filter_skeleton'; - -let expected; - -describe('Filter Manager', function () { - describe('Phrase filter builder', function () { - beforeEach(() => { - expected = cloneDeep(filterSkeleton); - }); - - it('should be a function', function () { - expect(buildQueryFilter).to.be.a(Function); - }); - - it('should return a query filter when passed a standard field', function () { - expected.query = { - foo: 'bar' - }; - expect(buildQueryFilter({ foo: 'bar' }, indexPattern.id)).to.eql(expected); - }); - - }); -}); diff --git a/packages/kbn-es-query/src/filters/__tests__/range.js b/packages/kbn-es-query/src/filters/__tests__/range.js deleted file mode 100644 index 9b23fc23908d4d..00000000000000 --- a/packages/kbn-es-query/src/filters/__tests__/range.js +++ /dev/null @@ -1,156 +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 { buildRangeFilter } from '../range'; -import expect from '@kbn/expect'; -import _ from 'lodash'; -import indexPattern from '../../__fixtures__/index_pattern_response.json'; -import filterSkeleton from '../../__fixtures__/filter_skeleton'; - -let expected; - -describe('Filter Manager', function () { - describe('Range filter builder', function () { - beforeEach(() => { - expected = _.cloneDeep(filterSkeleton); - }); - - it('should be a function', function () { - expect(buildRangeFilter).to.be.a(Function); - }); - - it('should return a range filter when passed a standard field', function () { - const field = getField(indexPattern, 'bytes'); - expected.range = { - bytes: { - gte: 1, - lte: 3 - } - }; - expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).to.eql(expected); - }); - - it('should return a script filter when passed a scripted field', function () { - const field = getField(indexPattern, 'script number'); - expected.meta.field = 'script number'; - _.set(expected, 'script.script', { - lang: 'expression', - source: '(' + field.script + ')>=gte && (' + field.script + ')<=lte', - params: { - value: '>=1 <=3', - gte: 1, - lte: 3 - } - }); - expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).to.eql(expected); - }); - - it('should wrap painless scripts in comparator lambdas', function () { - const field = getField(indexPattern, 'script date'); - const expected = `boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))} ` + - `boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}` + - `gte(() -> { ${field.script} }, params.gte) && ` + - `lte(() -> { ${field.script} }, params.lte)`; - - const inlineScript = buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern).script.script.source; - expect(inlineScript).to.be(expected); - }); - - it('should throw an error when gte and gt, or lte and lt are both passed', function () { - const field = getField(indexPattern, 'script number'); - expect(function () { - buildRangeFilter(field, { gte: 1, gt: 3 }, indexPattern); - }).to.throwError(); - expect(function () { - buildRangeFilter(field, { lte: 1, lt: 3 }, indexPattern); - }).to.throwError(); - }); - - it('to use the right operator for each of gte, gt, lt and lte', function () { - const field = getField(indexPattern, 'script number'); - _.each({ gte: '>=', gt: '>', lte: '<=', lt: '<' }, function (operator, key) { - const params = {}; - params[key] = 5; - const filter = buildRangeFilter(field, params, indexPattern); - - expect(filter.script.script.source).to.be( - '(' + field.script + ')' + operator + key); - expect(filter.script.script.params[key]).to.be(5); - expect(filter.script.script.params.value).to.be(operator + 5); - - }); - }); - - describe('when given params where one side is infinite', function () { - const field = getField(indexPattern, 'script number'); - let filter; - beforeEach(function () { - filter = buildRangeFilter(field, { gte: 0, lt: Infinity }, indexPattern); - }); - - describe('returned filter', function () { - it('is a script filter', function () { - expect(filter).to.have.property('script'); - }); - - it('contain a param for the finite side', function () { - expect(filter.script.script.params).to.have.property('gte', 0); - }); - - it('does not contain a param for the infinite side', function () { - expect(filter.script.script.params).not.to.have.property('lt'); - }); - - it('does not contain a script condition for the infinite side', function () { - const field = getField(indexPattern, 'script number'); - const script = field.script; - expect(filter.script.script.source).to.equal(`(${script})>=gte`); - }); - }); - }); - - describe('when given params where both sides are infinite', function () { - const field = getField(indexPattern, 'script number'); - let filter; - beforeEach(function () { - filter = buildRangeFilter( - field, { gte: -Infinity, lt: Infinity }, indexPattern); - }); - - describe('returned filter', function () { - it('is a match_all filter', function () { - expect(filter).not.to.have.property('script'); - expect(filter).to.have.property('match_all'); - }); - - it('does not contain params', function () { - expect(filter).not.to.have.property('params'); - }); - - it('meta field is set to field name', function () { - expect(filter.meta.field).to.equal('script number'); - }); - }); - }); - }); -}); - -function getField(indexPattern, name) { - return indexPattern.fields.find(field => field.name === name); -} diff --git a/packages/kbn-es-query/src/filters/exists.js b/packages/kbn-es-query/src/filters/exists.js deleted file mode 100644 index 0c82279fb44176..00000000000000 --- a/packages/kbn-es-query/src/filters/exists.js +++ /dev/null @@ -1,30 +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. - */ - -// Creates a filter where the given field exists -export function buildExistsFilter(field, indexPattern) { - return { - meta: { - index: indexPattern.id - }, - exists: { - field: field.name - } - }; -} diff --git a/packages/kbn-es-query/src/filters/index.d.ts b/packages/kbn-es-query/src/filters/index.d.ts deleted file mode 100644 index c05e32dbf07b95..00000000000000 --- a/packages/kbn-es-query/src/filters/index.d.ts +++ /dev/null @@ -1,51 +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 { CustomFilter, ExistsFilter, PhraseFilter, PhrasesFilter, RangeFilter } from './lib'; -import { RangeFilterParams } from './lib/range_filter'; - -export * from './lib'; - -// We can't import the real types from the data plugin, so need to either duplicate -// them here or figure out another solution, perhaps housing them in this package -type Field = any; -type IndexPattern = any; - -export function buildExistsFilter(field: Field, indexPattern: IndexPattern): ExistsFilter; - -export function buildPhraseFilter( - field: Field, - value: string, - indexPattern: IndexPattern -): PhraseFilter; - -export function buildPhrasesFilter( - field: Field, - values: string[], - indexPattern: IndexPattern -): PhrasesFilter; - -export function buildQueryFilter(query: any, index: string, alias?: string): CustomFilter; - -export function buildRangeFilter( - field: Field, - params: RangeFilterParams, - indexPattern: IndexPattern, - formattedValue?: string -): RangeFilter; diff --git a/packages/kbn-es-query/src/filters/index.js b/packages/kbn-es-query/src/filters/index.js deleted file mode 100644 index d7d092eabd8a2f..00000000000000 --- a/packages/kbn-es-query/src/filters/index.js +++ /dev/null @@ -1,25 +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. - */ - -export * from './exists'; -export * from './phrase'; -export * from './phrases'; -export * from './query'; -export * from './range'; -export * from './lib'; diff --git a/packages/kbn-es-query/src/filters/lib/index.ts b/packages/kbn-es-query/src/filters/lib/index.ts deleted file mode 100644 index ea023987103414..00000000000000 --- a/packages/kbn-es-query/src/filters/lib/index.ts +++ /dev/null @@ -1,95 +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. - */ - -// The interface the other filters extend -export * from './meta_filter'; - -// The actual filter types -import { CustomFilter } from './custom_filter'; -import { ExistsFilter, isExistsFilter } from './exists_filter'; -import { GeoBoundingBoxFilter, isGeoBoundingBoxFilter } from './geo_bounding_box_filter'; -import { GeoPolygonFilter, isGeoPolygonFilter } from './geo_polygon_filter'; -import { - PhraseFilter, - isPhraseFilter, - isScriptedPhraseFilter, - getPhraseFilterField, - getPhraseFilterValue, -} from './phrase_filter'; -import { PhrasesFilter, isPhrasesFilter } from './phrases_filter'; -import { QueryStringFilter, isQueryStringFilter } from './query_string_filter'; -import { - RangeFilter, - isRangeFilter, - isScriptedRangeFilter, - RangeFilterParams, -} from './range_filter'; -import { MatchAllFilter, isMatchAllFilter } from './match_all_filter'; -import { MissingFilter, isMissingFilter } from './missing_filter'; - -export { - CustomFilter, - ExistsFilter, - isExistsFilter, - GeoBoundingBoxFilter, - isGeoBoundingBoxFilter, - GeoPolygonFilter, - isGeoPolygonFilter, - PhraseFilter, - isPhraseFilter, - isScriptedPhraseFilter, - getPhraseFilterField, - getPhraseFilterValue, - PhrasesFilter, - isPhrasesFilter, - QueryStringFilter, - isQueryStringFilter, - RangeFilter, - isRangeFilter, - isScriptedRangeFilter, - RangeFilterParams, - MatchAllFilter, - isMatchAllFilter, - MissingFilter, - isMissingFilter, -}; - -// Any filter associated with a field (used in the filter bar/editor) -export type FieldFilter = - | ExistsFilter - | GeoBoundingBoxFilter - | GeoPolygonFilter - | PhraseFilter - | PhrasesFilter - | RangeFilter - | MatchAllFilter - | MissingFilter; - -export enum FILTERS { - CUSTOM = 'custom', - PHRASES = 'phrases', - PHRASE = 'phrase', - EXISTS = 'exists', - MATCH_ALL = 'match_all', - MISSING = 'missing', - QUERY_STRING = 'query_string', - RANGE = 'range', - GEO_BOUNDING_BOX = 'geo_bounding_box', - GEO_POLYGON = 'geo_polygon', -} diff --git a/packages/kbn-es-query/src/filters/lib/phrase_filter.ts b/packages/kbn-es-query/src/filters/lib/phrase_filter.ts deleted file mode 100644 index 124a5da3724871..00000000000000 --- a/packages/kbn-es-query/src/filters/lib/phrase_filter.ts +++ /dev/null @@ -1,65 +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 { get, isPlainObject } from 'lodash'; -import { Filter, FilterMeta } from './meta_filter'; - -export type PhraseFilterMeta = FilterMeta & { - params: { - query: string; // The unformatted value - }; - script?: { - script: { - params: any; - }; - }; - field?: any; -}; - -export type PhraseFilter = Filter & { - meta: PhraseFilterMeta; -}; - -type PhraseFilterValue = string | number | boolean; - -export const isPhraseFilter = (filter: any): filter is PhraseFilter => { - const isMatchPhraseQuery = filter && filter.query && filter.query.match_phrase; - - const isDeprecatedMatchPhraseQuery = - filter && - filter.query && - filter.query.match && - Object.values(filter.query.match).find((params: any) => params.type === 'phrase'); - - return !!(isMatchPhraseQuery || isDeprecatedMatchPhraseQuery); -}; - -export const isScriptedPhraseFilter = (filter: any): filter is PhraseFilter => - Boolean(get(filter, 'script.script.params.value')); - -export const getPhraseFilterField = (filter: PhraseFilter) => { - const queryConfig = filter.query.match_phrase || filter.query.match; - return Object.keys(queryConfig)[0]; -}; - -export const getPhraseFilterValue = (filter: PhraseFilter): PhraseFilterValue => { - const queryConfig = filter.query.match_phrase || filter.query.match; - const queryValue = Object.values(queryConfig)[0] as any; - return isPlainObject(queryValue) ? queryValue.query : queryValue; -}; diff --git a/packages/kbn-es-query/src/filters/lib/phrases_filter.ts b/packages/kbn-es-query/src/filters/lib/phrases_filter.ts deleted file mode 100644 index 213afb409a0a69..00000000000000 --- a/packages/kbn-es-query/src/filters/lib/phrases_filter.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Filter, FilterMeta } from './meta_filter'; - -export type PhrasesFilterMeta = FilterMeta & { - params: string[]; // The unformatted values - field?: string; -}; - -export type PhrasesFilter = Filter & { - meta: PhrasesFilterMeta; -}; - -export const isPhrasesFilter = (filter: any): filter is PhrasesFilter => - filter && filter.meta.type === 'phrases'; diff --git a/packages/kbn-es-query/src/filters/lib/range_filter.ts b/packages/kbn-es-query/src/filters/lib/range_filter.ts deleted file mode 100644 index fc8d05d575d597..00000000000000 --- a/packages/kbn-es-query/src/filters/lib/range_filter.ts +++ /dev/null @@ -1,58 +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 { get, keys } from 'lodash'; -import { Filter, FilterMeta } from './meta_filter'; - -export interface RangeFilterParams { - from?: number | string; - to?: number | string; - gt?: number | string; - lt?: number | string; - gte?: number | string; - lte?: number | string; - format?: string; -} - -export type RangeFilterMeta = FilterMeta & { - params: RangeFilterParams; - field?: any; -}; - -export type RangeFilter = Filter & { - meta: RangeFilterMeta; - script?: { - script: { - params: any; - }; - }; - range: { [key: string]: RangeFilterParams }; -}; - -const hasRangeKeys = (params: RangeFilterParams) => - Boolean( - keys(params).find((key: string) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key)) - ); - -export const isRangeFilter = (filter: any): filter is RangeFilter => filter && filter.range; - -export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => { - const params: RangeFilterParams = get(filter, 'script.script.params', {}); - - return hasRangeKeys(params); -}; diff --git a/packages/kbn-es-query/src/filters/phrase.js b/packages/kbn-es-query/src/filters/phrase.js deleted file mode 100644 index f0134f289ad9d2..00000000000000 --- a/packages/kbn-es-query/src/filters/phrase.js +++ /dev/null @@ -1,85 +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. - */ - -// Creates an filter where the given field matches the given value -export function buildPhraseFilter(field, value, indexPattern) { - const filter = { meta: { index: indexPattern.id } }; - const convertedValue = getConvertedValueForField(field, value); - - if (field.scripted) { - filter.script = getPhraseScript(field, value); - filter.meta.field = field.name; - } else { - filter.query = { match_phrase: {} }; - filter.query.match_phrase[field.name] = convertedValue; - } - return filter; -} - -export function getPhraseScript(field, value) { - const convertedValue = getConvertedValueForField(field, value); - const script = buildInlineScriptForPhraseFilter(field); - - return { - script: { - source: script, - lang: field.lang, - params: { - value: convertedValue - } - } - }; -} - -// See https://github.com/elastic/elasticsearch/issues/20941 and https://github.com/elastic/kibana/issues/8677 -// and https://github.com/elastic/elasticsearch/pull/22201 -// for the reason behind this change. Aggs now return boolean buckets with a key of 1 or 0. -export function getConvertedValueForField(field, value) { - if (typeof value !== 'boolean' && field.type === 'boolean') { - if ([1, 'true'].includes(value)) { - return true; - } - else if ([0, 'false'].includes(value)) { - return false; - } - else { - throw new Error(`${value} is not a valid boolean value for boolean field ${field.name}`); - } - } - return value; -} - -/** - * Takes a scripted field and returns an inline script appropriate for use in a script query. - * Handles lucene expression and Painless scripts. Other langs aren't guaranteed to generate valid - * scripts. - * - * @param {object} scriptedField A Field object representing a scripted field - * @returns {string} The inline script string - */ -export function buildInlineScriptForPhraseFilter(scriptedField) { - // We must wrap painless scripts in a lambda in case they're more than a simple expression - if (scriptedField.lang === 'painless') { - return `boolean compare(Supplier s, def v) {return s.get() == v;}` + - `compare(() -> { ${scriptedField.script} }, params.value);`; - } - else { - return `(${scriptedField.script}) == value`; - } -} diff --git a/packages/kbn-es-query/src/filters/phrases.js b/packages/kbn-es-query/src/filters/phrases.js deleted file mode 100644 index f02b3763f37bb7..00000000000000 --- a/packages/kbn-es-query/src/filters/phrases.js +++ /dev/null @@ -1,63 +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 { getPhraseScript } from './phrase'; - -// Creates a filter where the given field matches one or more of the given values -// params should be an array of values -export function buildPhrasesFilter(field, params, indexPattern) { - const index = indexPattern.id; - const type = 'phrases'; - const key = field.name; - const value = params - .map(value => format(field, value)) - .join(', '); - - const filter = { - meta: { index, type, key, value, params } - }; - - let should; - if (field.scripted) { - should = params.map((value) => ({ - script: getPhraseScript(field, value) - })); - } else { - should = params.map((value) => ({ - match_phrase: { - [field.name]: value - } - })); - } - - filter.query = { - bool: { - should, - minimum_should_match: 1 - } - }; - - return filter; -} - -function format(field, value) { - return field && field.format && field.format.convert - ? field.format.convert(value) - : value; -} diff --git a/packages/kbn-es-query/src/filters/query.js b/packages/kbn-es-query/src/filters/query.js deleted file mode 100644 index ded877231bf67e..00000000000000 --- a/packages/kbn-es-query/src/filters/query.js +++ /dev/null @@ -1,34 +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. - */ - -// Creates a filter corresponding to a raw Elasticsearch query DSL object -export function buildQueryFilter(query, index, alias) { - const filter = { - query: query, - meta: { - index, - } - }; - - if (alias) { - filter.meta.alias = alias; - } - - return filter; -} diff --git a/packages/kbn-es-query/src/filters/range.js b/packages/kbn-es-query/src/filters/range.js deleted file mode 100644 index 357f9209c50de3..00000000000000 --- a/packages/kbn-es-query/src/filters/range.js +++ /dev/null @@ -1,121 +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 _ from 'lodash'; -const OPERANDS_IN_RANGE = 2; -const operators = { - gt: '>', - gte: '>=', - lte: '<=', - lt: '<', -}; -const comparators = { - gt: 'boolean gt(Supplier s, def v) {return s.get() > v}', - gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}', - lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}', - lt: 'boolean lt(Supplier s, def v) {return s.get() < v}', -}; - -const dateComparators = { - gt: 'boolean gt(Supplier s, def v) {return s.get().toInstant().isAfter(Instant.parse(v))}', - gte: 'boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))}', - lte: 'boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}', - lt: 'boolean lt(Supplier s, def v) {return s.get().toInstant().isBefore(Instant.parse(v))}', -}; - -function formatValue(field, params) { - return _.map(params, (val, key) => operators[key] + format(field, val)).join(' '); -} - -// Creates a filter where the value for the given field is in the given range -// params should be an object containing `lt`, `lte`, `gt`, and/or `gte` -export function buildRangeFilter(field, params, indexPattern, formattedValue) { - const filter = { meta: { index: indexPattern.id } }; - if (formattedValue) filter.meta.formattedValue = formattedValue; - - params = _.mapValues(params, (value) => { - return (field.type === 'number') ? parseFloat(value) : value; - }); - - if ('gte' in params && 'gt' in params) throw new Error('gte and gt are mutually exclusive'); - if ('lte' in params && 'lt' in params) throw new Error('lte and lt are mutually exclusive'); - - const totalInfinite = ['gt', 'lt'].reduce((totalInfinite, op) => { - const key = op in params ? op : `${op}e`; - const isInfinite = Math.abs(params[key]) === Infinity; - - if (isInfinite) { - totalInfinite++; - delete params[key]; - } - - return totalInfinite; - }, 0); - - if (totalInfinite === OPERANDS_IN_RANGE) { - filter.match_all = {}; - filter.meta.field = field.name; - } else if (field.scripted) { - filter.script = getRangeScript(field, params); - filter.script.script.params.value = formatValue(field, filter.script.script.params); - - filter.meta.field = field.name; - } else { - filter.range = {}; - filter.range[field.name] = params; - } - - return filter; -} - -export function getRangeScript(field, params) { - const knownParams = _.pick(params, (val, key) => { - return key in operators; - }); - let script = _.map(knownParams, function (val, key) { - return '(' + field.script + ')' + operators[key] + key; - }).join(' && '); - - // We must wrap painless scripts in a lambda in case they're more than a simple expression - if (field.lang === 'painless') { - const comp = field.type === 'date' ? dateComparators : comparators; - const currentComparators = _.reduce(knownParams, (acc, val, key) => acc.concat(comp[key]), []).join(' '); - - const comparisons = _.map(knownParams, function (val, key) { - return `${key}(() -> { ${field.script} }, params.${key})`; - }).join(' && '); - - script = `${currentComparators}${comparisons}`; - } - - return { - script: { - source: script, - params: knownParams, - lang: field.lang - } - }; -} - -function format(field, value) { - return field && field.format && field.format.convert - ? field.format.convert(value) - : value; -} - diff --git a/packages/kbn-es-query/src/index.d.ts b/packages/kbn-es-query/src/index.d.ts index ca4455da33f45d..c06cef6367fe78 100644 --- a/packages/kbn-es-query/src/index.d.ts +++ b/packages/kbn-es-query/src/index.d.ts @@ -19,4 +19,3 @@ export * from './es_query'; export * from './kuery'; -export * from './filters'; diff --git a/packages/kbn-es-query/src/index.js b/packages/kbn-es-query/src/index.js index 086b2f6db8d0dc..963999bd0999b2 100644 --- a/packages/kbn-es-query/src/index.js +++ b/packages/kbn-es-query/src/index.js @@ -18,5 +18,4 @@ */ export * from './kuery'; -export * from './filters'; export * from './es_query'; diff --git a/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js index 1ba10ddca54ac1..3cbe1203bc5330 100644 --- a/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js +++ b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js @@ -198,6 +198,68 @@ describe('kuery AST API', function () { expect(actual).to.eql(expected); }); + it('should support nested queries indicated by curly braces', () => { + const expected = nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'foo') + ); + const actual = ast.fromKueryExpression('nestedField:{ childOfNested: foo }'); + expect(actual).to.eql(expected); + }); + + it('should support nested subqueries and subqueries inside nested queries', () => { + const expected = nodeTypes.function.buildNode( + 'and', + [ + nodeTypes.function.buildNode('is', 'response', '200'), + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', 'childOfNested', 'foo'), + nodeTypes.function.buildNode('is', 'childOfNested', 'bar'), + ]) + )]); + const actual = ast.fromKueryExpression('response:200 and nestedField:{ childOfNested:foo or childOfNested:bar }'); + expect(actual).to.eql(expected); + }); + + it('should support nested sub-queries inside paren groups', () => { + const expected = nodeTypes.function.buildNode( + 'and', + [ + nodeTypes.function.buildNode('is', 'response', '200'), + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'foo') + ), + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'bar') + ), + ]) + ]); + const actual = ast.fromKueryExpression('response:200 and ( nestedField:{ childOfNested:foo } or nestedField:{ childOfNested:bar } )'); + expect(actual).to.eql(expected); + }); + + it('should support nested groups inside other nested groups', () => { + const expected = nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode( + 'nested', + 'nestedChild', + nodeTypes.function.buildNode('is', 'doublyNestedChild', 'foo') + ) + ); + const actual = ast.fromKueryExpression('nestedField:{ nestedChild:{ doublyNestedChild:foo } }'); + expect(actual).to.eql(expected); + }); }); describe('fromLiteralExpression', function () { diff --git a/packages/kbn-es-query/src/kuery/ast/ast.js b/packages/kbn-es-query/src/kuery/ast/ast.js index 0bf2b258ba1f40..1688995d46f80e 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.js +++ b/packages/kbn-es-query/src/kuery/ast/ast.js @@ -63,12 +63,12 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) { * IndexPattern isn't required, but if you pass one in, we can be more intelligent * about how we craft the queries (e.g. scripted fields) */ -export function toElasticsearchQuery(node, indexPattern, config = {}) { +export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { if (!node || !node.type || !nodeTypes[node.type]) { return toElasticsearchQuery(nodeTypes.function.buildNode('and', [])); } - return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern, config); + return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern, config, context); } export function doesKueryExpressionHaveLuceneSyntaxError(expression) { diff --git a/packages/kbn-es-query/src/kuery/ast/kuery.js b/packages/kbn-es-query/src/kuery/ast/kuery.js index d39145df790731..f745f01873bae4 100644 --- a/packages/kbn-es-query/src/kuery/ast/kuery.js +++ b/packages/kbn-es-query/src/kuery/ast/kuery.js @@ -74,8 +74,30 @@ module.exports = (function() { } return query; }, - peg$c10 = { type: "other", description: "fieldName" }, - peg$c11 = function(field, operator, value) { + peg$c10 = ":", + peg$c11 = { type: "literal", value: ":", description: "\":\"" }, + peg$c12 = "{", + peg$c13 = { type: "literal", value: "{", description: "\"{\"" }, + peg$c14 = "}", + peg$c15 = { type: "literal", value: "}", description: "\"}\"" }, + peg$c16 = function(field, query, trailing) { + if (query.type === 'cursor') { + return { + ...query, + nestedPath: query.nestedPath ? `${field.value}.${query.nestedPath}` : field.value, + } + }; + + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return buildFunctionNode('nested', [field, query]); + }, + peg$c17 = { type: "other", description: "fieldName" }, + peg$c18 = function(field, operator, value) { if (value.type === 'cursor') { return { ...value, @@ -85,9 +107,7 @@ module.exports = (function() { const range = buildNamedArgNode(operator, value); return buildFunctionNode('range', [field, range]); }, - peg$c12 = ":", - peg$c13 = { type: "literal", value: ":", description: "\":\"" }, - peg$c14 = function(field, partial) { + peg$c19 = function(field, partial) { if (partial.type === 'cursor') { return { ...partial, @@ -97,7 +117,7 @@ module.exports = (function() { } return partial(field); }, - peg$c15 = function(partial) { + peg$c20 = function(partial) { if (partial.type === 'cursor') { const fieldName = `${partial.prefix}${partial.suffix}`.trim(); return { @@ -109,7 +129,7 @@ module.exports = (function() { const field = buildLiteralNode(null); return partial(field); }, - peg$c16 = function(partial, trailing) { + peg$c21 = function(partial, trailing) { if (trailing.type === 'cursor') { return { ...trailing, @@ -118,7 +138,7 @@ module.exports = (function() { } return partial; }, - peg$c17 = function(partialLeft, partialRight) { + peg$c22 = function(partialLeft, partialRight) { const cursor = [partialLeft, partialRight].find(node => node.type === 'cursor'); if (cursor) { return { @@ -128,7 +148,7 @@ module.exports = (function() { } return (field) => buildFunctionNode('or', [partialLeft(field), partialRight(field)]); }, - peg$c18 = function(partialLeft, partialRight) { + peg$c23 = function(partialLeft, partialRight) { const cursor = [partialLeft, partialRight].find(node => node.type === 'cursor'); if (cursor) { return { @@ -138,7 +158,7 @@ module.exports = (function() { } return (field) => buildFunctionNode('and', [partialLeft(field), partialRight(field)]); }, - peg$c19 = function(partial) { + peg$c24 = function(partial) { if (partial.type === 'cursor') { return { ...list, @@ -147,13 +167,13 @@ module.exports = (function() { } return (field) => buildFunctionNode('not', [partial(field)]); }, - peg$c20 = { type: "other", description: "value" }, - peg$c21 = function(value) { + peg$c25 = { type: "other", description: "value" }, + peg$c26 = function(value) { if (value.type === 'cursor') return value; const isPhrase = buildLiteralNode(true); return (field) => buildFunctionNode('is', [field, value, isPhrase]); }, - peg$c22 = function(value) { + peg$c27 = function(value) { if (value.type === 'cursor') return value; if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) { @@ -163,19 +183,19 @@ module.exports = (function() { const isPhrase = buildLiteralNode(false); return (field) => buildFunctionNode('is', [field, value, isPhrase]); }, - peg$c23 = { type: "other", description: "OR" }, - peg$c24 = "or", - peg$c25 = { type: "literal", value: "or", description: "\"or\"" }, - peg$c26 = { type: "other", description: "AND" }, - peg$c27 = "and", - peg$c28 = { type: "literal", value: "and", description: "\"and\"" }, - peg$c29 = { type: "other", description: "NOT" }, - peg$c30 = "not", - peg$c31 = { type: "literal", value: "not", description: "\"not\"" }, - peg$c32 = { type: "other", description: "literal" }, - peg$c33 = "\"", - peg$c34 = { type: "literal", value: "\"", description: "\"\\\"\"" }, - peg$c35 = function(prefix, cursor, suffix) { + peg$c28 = { type: "other", description: "OR" }, + peg$c29 = "or", + peg$c30 = { type: "literal", value: "or", description: "\"or\"" }, + peg$c31 = { type: "other", description: "AND" }, + peg$c32 = "and", + peg$c33 = { type: "literal", value: "and", description: "\"and\"" }, + peg$c34 = { type: "other", description: "NOT" }, + peg$c35 = "not", + peg$c36 = { type: "literal", value: "not", description: "\"not\"" }, + peg$c37 = { type: "other", description: "literal" }, + peg$c38 = "\"", + peg$c39 = { type: "literal", value: "\"", description: "\"\\\"\"" }, + peg$c40 = function(prefix, cursor, suffix) { const { start, end } = location(); return { type: 'cursor', @@ -186,17 +206,17 @@ module.exports = (function() { text: text().replace(cursor, '') }; }, - peg$c36 = function(chars) { + peg$c41 = function(chars) { return buildLiteralNode(chars.join('')); }, - peg$c37 = "\\", - peg$c38 = { type: "literal", value: "\\", description: "\"\\\\\"" }, - peg$c39 = /^[\\"]/, - peg$c40 = { type: "class", value: "[\\\\\"]", description: "[\\\\\"]" }, - peg$c41 = function(char) { return char; }, - peg$c42 = /^[^"]/, - peg$c43 = { type: "class", value: "[^\"]", description: "[^\"]" }, - peg$c44 = function(chars) { + peg$c42 = "\\", + peg$c43 = { type: "literal", value: "\\", description: "\"\\\\\"" }, + peg$c44 = /^[\\"]/, + peg$c45 = { type: "class", value: "[\\\\\"]", description: "[\\\\\"]" }, + peg$c46 = function(char) { return char; }, + peg$c47 = /^[^"]/, + peg$c48 = { type: "class", value: "[^\"]", description: "[^\"]" }, + peg$c49 = function(chars) { const sequence = chars.join('').trim(); if (sequence === 'null') return buildLiteralNode(null); if (sequence === 'true') return buildLiteralNode(true); @@ -206,108 +226,104 @@ module.exports = (function() { const value = isNaN(number) ? sequence : number; return buildLiteralNode(value); }, - peg$c45 = { type: "any", description: "any character" }, - peg$c46 = "*", - peg$c47 = { type: "literal", value: "*", description: "\"*\"" }, - peg$c48 = function() { return wildcardSymbol; }, - peg$c49 = "\\t", - peg$c50 = { type: "literal", value: "\\t", description: "\"\\\\t\"" }, - peg$c51 = function() { return '\t'; }, - peg$c52 = "\\r", - peg$c53 = { type: "literal", value: "\\r", description: "\"\\\\r\"" }, - peg$c54 = function() { return '\r'; }, - peg$c55 = "\\n", - peg$c56 = { type: "literal", value: "\\n", description: "\"\\\\n\"" }, - peg$c57 = function() { return '\n'; }, - peg$c58 = function(keyword) { return keyword; }, - peg$c59 = /^[\\():<>"*]/, - peg$c60 = { type: "class", value: "[\\\\():<>\"*]", description: "[\\\\():<>\"*]" }, - peg$c61 = "<=", - peg$c62 = { type: "literal", value: "<=", description: "\"<=\"" }, - peg$c63 = function() { return 'lte'; }, - peg$c64 = ">=", - peg$c65 = { type: "literal", value: ">=", description: "\">=\"" }, - peg$c66 = function() { return 'gte'; }, - peg$c67 = "<", - peg$c68 = { type: "literal", value: "<", description: "\"<\"" }, - peg$c69 = function() { return 'lt'; }, - peg$c70 = ">", - peg$c71 = { type: "literal", value: ">", description: "\">\"" }, - peg$c72 = function() { return 'gt'; }, - peg$c73 = { type: "other", description: "whitespace" }, - peg$c74 = /^[ \t\r\n]/, - peg$c75 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, - peg$c76 = function() { return parseCursor; }, - peg$c77 = "@kuery-cursor@", - peg$c78 = { type: "literal", value: "@kuery-cursor@", description: "\"@kuery-cursor@\"" }, - peg$c79 = function() { return cursorSymbol; }, - peg$c80 = "||", - peg$c81 = { type: "literal", value: "||", description: "\"||\"" }, - peg$c82 = function() { + peg$c50 = { type: "any", description: "any character" }, + peg$c51 = "*", + peg$c52 = { type: "literal", value: "*", description: "\"*\"" }, + peg$c53 = function() { return wildcardSymbol; }, + peg$c54 = "\\t", + peg$c55 = { type: "literal", value: "\\t", description: "\"\\\\t\"" }, + peg$c56 = function() { return '\t'; }, + peg$c57 = "\\r", + peg$c58 = { type: "literal", value: "\\r", description: "\"\\\\r\"" }, + peg$c59 = function() { return '\r'; }, + peg$c60 = "\\n", + peg$c61 = { type: "literal", value: "\\n", description: "\"\\\\n\"" }, + peg$c62 = function() { return '\n'; }, + peg$c63 = function(keyword) { return keyword; }, + peg$c64 = /^[\\():<>"*{}]/, + peg$c65 = { type: "class", value: "[\\\\():<>\"*{}]", description: "[\\\\():<>\"*{}]" }, + peg$c66 = "<=", + peg$c67 = { type: "literal", value: "<=", description: "\"<=\"" }, + peg$c68 = function() { return 'lte'; }, + peg$c69 = ">=", + peg$c70 = { type: "literal", value: ">=", description: "\">=\"" }, + peg$c71 = function() { return 'gte'; }, + peg$c72 = "<", + peg$c73 = { type: "literal", value: "<", description: "\"<\"" }, + peg$c74 = function() { return 'lt'; }, + peg$c75 = ">", + peg$c76 = { type: "literal", value: ">", description: "\">\"" }, + peg$c77 = function() { return 'gt'; }, + peg$c78 = { type: "other", description: "whitespace" }, + peg$c79 = /^[ \t\r\n]/, + peg$c80 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, + peg$c81 = function() { return parseCursor; }, + peg$c82 = "@kuery-cursor@", + peg$c83 = { type: "literal", value: "@kuery-cursor@", description: "\"@kuery-cursor@\"" }, + peg$c84 = function() { return cursorSymbol; }, + peg$c85 = "||", + peg$c86 = { type: "literal", value: "||", description: "\"||\"" }, + peg$c87 = function() { error('LuceneOr'); }, - peg$c83 = "&&", - peg$c84 = { type: "literal", value: "&&", description: "\"&&\"" }, - peg$c85 = function() { + peg$c88 = "&&", + peg$c89 = { type: "literal", value: "&&", description: "\"&&\"" }, + peg$c90 = function() { error('LuceneAnd'); }, - peg$c86 = "+", - peg$c87 = { type: "literal", value: "+", description: "\"+\"" }, - peg$c88 = "-", - peg$c89 = { type: "literal", value: "-", description: "\"-\"" }, - peg$c90 = function() { + peg$c91 = "+", + peg$c92 = { type: "literal", value: "+", description: "\"+\"" }, + peg$c93 = "-", + peg$c94 = { type: "literal", value: "-", description: "\"-\"" }, + peg$c95 = function() { error('LuceneNot'); }, - peg$c91 = "!", - peg$c92 = { type: "literal", value: "!", description: "\"!\"" }, - peg$c93 = "_exists_", - peg$c94 = { type: "literal", value: "_exists_", description: "\"_exists_\"" }, - peg$c95 = function() { + peg$c96 = "!", + peg$c97 = { type: "literal", value: "!", description: "\"!\"" }, + peg$c98 = "_exists_", + peg$c99 = { type: "literal", value: "_exists_", description: "\"_exists_\"" }, + peg$c100 = function() { error('LuceneExists'); }, - peg$c96 = function() { + peg$c101 = function() { error('LuceneRange'); }, - peg$c97 = "?", - peg$c98 = { type: "literal", value: "?", description: "\"?\"" }, - peg$c99 = function() { + peg$c102 = "?", + peg$c103 = { type: "literal", value: "?", description: "\"?\"" }, + peg$c104 = function() { error('LuceneWildcard'); }, - peg$c100 = "/", - peg$c101 = { type: "literal", value: "/", description: "\"/\"" }, - peg$c102 = /^[^\/]/, - peg$c103 = { type: "class", value: "[^/]", description: "[^/]" }, - peg$c104 = function() { + peg$c105 = "/", + peg$c106 = { type: "literal", value: "/", description: "\"/\"" }, + peg$c107 = /^[^\/]/, + peg$c108 = { type: "class", value: "[^/]", description: "[^/]" }, + peg$c109 = function() { error('LuceneRegex'); }, - peg$c105 = "~", - peg$c106 = { type: "literal", value: "~", description: "\"~\"" }, - peg$c107 = /^[0-9]/, - peg$c108 = { type: "class", value: "[0-9]", description: "[0-9]" }, - peg$c109 = function() { + peg$c110 = "~", + peg$c111 = { type: "literal", value: "~", description: "\"~\"" }, + peg$c112 = /^[0-9]/, + peg$c113 = { type: "class", value: "[0-9]", description: "[0-9]" }, + peg$c114 = function() { error('LuceneFuzzy'); }, - peg$c110 = function() { + peg$c115 = function() { error('LuceneProximity'); }, - peg$c111 = "^", - peg$c112 = { type: "literal", value: "^", description: "\"^\"" }, - peg$c113 = function() { + peg$c116 = "^", + peg$c117 = { type: "literal", value: "^", description: "\"^\"" }, + peg$c118 = function() { error('LuceneBoost'); }, - peg$c114 = function() { return char; }, - peg$c115 = "=", - peg$c116 = { type: "literal", value: "=", description: "\"=\"" }, - peg$c117 = "{", - peg$c118 = { type: "literal", value: "{", description: "\"{\"" }, - peg$c119 = "}", - peg$c120 = { type: "literal", value: "}", description: "\"}\"" }, - peg$c121 = "[", - peg$c122 = { type: "literal", value: "[", description: "\"[\"" }, - peg$c123 = "]", - peg$c124 = { type: "literal", value: "]", description: "\"]\"" }, - peg$c125 = "TO", - peg$c126 = { type: "literal", value: "TO", description: "\"TO\"" }, + peg$c119 = function() { return char; }, + peg$c120 = "=", + peg$c121 = { type: "literal", value: "=", description: "\"=\"" }, + peg$c122 = "[", + peg$c123 = { type: "literal", value: "[", description: "\"[\"" }, + peg$c124 = "]", + peg$c125 = { type: "literal", value: "]", description: "\"]\"" }, + peg$c126 = "TO", + peg$c127 = { type: "literal", value: "TO", description: "\"TO\"" }, peg$currPos = 0, peg$savedPos = 0, @@ -698,6 +714,107 @@ module.exports = (function() { peg$currPos = s0; s0 = peg$FAILED; } + if (s0 === peg$FAILED) { + s0 = peg$parseNestedQuery(); + } + + return s0; + } + + function peg$parseNestedQuery() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + s1 = peg$parseField(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c10; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c11); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseSpace(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseSpace(); + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 123) { + s5 = peg$c12; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c13); } + } + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseSpace(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseSpace(); + } + if (s6 !== peg$FAILED) { + s7 = peg$parseOrQuery(); + if (s7 !== peg$FAILED) { + s8 = peg$parseOptionalSpace(); + if (s8 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s9 = peg$c14; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c15); } + } + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c16(s1, s7, s8); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } if (s0 === peg$FAILED) { s0 = peg$parseExpression(); } @@ -727,7 +844,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c10); } + if (peg$silentFails === 0) { peg$fail(peg$c17); } } return s0; @@ -758,7 +875,7 @@ module.exports = (function() { s5 = peg$parseLiteral(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c11(s1, s3, s5); + s1 = peg$c18(s1, s3, s5); s0 = s1; } else { peg$currPos = s0; @@ -798,11 +915,11 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c12; + s3 = peg$c10; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s3 !== peg$FAILED) { s4 = []; @@ -815,7 +932,7 @@ module.exports = (function() { s5 = peg$parseListOfValues(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c14(s1, s5); + s1 = peg$c19(s1, s5); s0 = s1; } else { peg$currPos = s0; @@ -848,7 +965,7 @@ module.exports = (function() { s1 = peg$parseValue(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c15(s1); + s1 = peg$c20(s1); } s0 = s1; @@ -887,7 +1004,7 @@ module.exports = (function() { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c16(s3, s4); + s1 = peg$c21(s3, s4); s0 = s1; } else { peg$currPos = s0; @@ -927,7 +1044,7 @@ module.exports = (function() { s3 = peg$parseOrListOfValues(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c17(s1, s3); + s1 = peg$c22(s1, s3); s0 = s1; } else { peg$currPos = s0; @@ -959,7 +1076,7 @@ module.exports = (function() { s3 = peg$parseAndListOfValues(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c18(s1, s3); + s1 = peg$c23(s1, s3); s0 = s1; } else { peg$currPos = s0; @@ -989,7 +1106,7 @@ module.exports = (function() { s2 = peg$parseListOfValues(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c19(s2); + s1 = peg$c24(s2); s0 = s1; } else { peg$currPos = s0; @@ -1014,7 +1131,7 @@ module.exports = (function() { s1 = peg$parseQuotedString(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c21(s1); + s1 = peg$c26(s1); } s0 = s1; if (s0 === peg$FAILED) { @@ -1022,14 +1139,14 @@ module.exports = (function() { s1 = peg$parseUnquotedLiteral(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c22(s1); + s1 = peg$c27(s1); } s0 = s1; } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c20); } + if (peg$silentFails === 0) { peg$fail(peg$c25); } } return s0; @@ -1051,12 +1168,12 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c24) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c29) { s2 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c25); } + if (peg$silentFails === 0) { peg$fail(peg$c30); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1110,7 +1227,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c23); } + if (peg$silentFails === 0) { peg$fail(peg$c28); } } return s0; @@ -1132,12 +1249,12 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c27) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c32) { s2 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c33); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1191,7 +1308,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c26); } + if (peg$silentFails === 0) { peg$fail(peg$c31); } } return s0; @@ -1202,12 +1319,12 @@ module.exports = (function() { peg$silentFails++; s0 = peg$currPos; - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c30) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c35) { s1 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c31); } + if (peg$silentFails === 0) { peg$fail(peg$c36); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1257,7 +1374,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c29); } + if (peg$silentFails === 0) { peg$fail(peg$c34); } } return s0; @@ -1274,7 +1391,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } + if (peg$silentFails === 0) { peg$fail(peg$c37); } } return s0; @@ -1285,11 +1402,11 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c33; + s1 = peg$c38; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1309,15 +1426,15 @@ module.exports = (function() { } if (s4 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s5 = peg$c33; + s5 = peg$c38; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c35(s2, s3, s4); + s1 = peg$c40(s2, s3, s4); s0 = s1; } else { peg$currPos = s0; @@ -1342,11 +1459,11 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c33; + s1 = peg$c38; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1357,15 +1474,15 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c33; + s3 = peg$c38; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c36(s2); + s1 = peg$c41(s2); s0 = s1; } else { peg$currPos = s0; @@ -1391,23 +1508,23 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { - if (peg$c39.test(input.charAt(peg$currPos))) { + if (peg$c44.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c40); } + if (peg$silentFails === 0) { peg$fail(peg$c45); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s2); + s1 = peg$c46(s2); s0 = s1; } else { peg$currPos = s0; @@ -1430,16 +1547,16 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (peg$c42.test(input.charAt(peg$currPos))) { + if (peg$c47.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c43); } + if (peg$silentFails === 0) { peg$fail(peg$c48); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s2); + s1 = peg$c46(s2); s0 = s1; } else { peg$currPos = s0; @@ -1476,7 +1593,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c35(s1, s2, s3); + s1 = peg$c40(s1, s2, s3); s0 = s1; } else { peg$currPos = s0; @@ -1504,7 +1621,7 @@ module.exports = (function() { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c44(s1); + s1 = peg$c49(s1); } s0 = s1; } @@ -1562,11 +1679,11 @@ module.exports = (function() { peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } + if (peg$silentFails === 0) { peg$fail(peg$c50); } } if (s4 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s4); + s1 = peg$c46(s4); s0 = s1; } else { peg$currPos = s0; @@ -1597,15 +1714,15 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 42) { - s1 = peg$c46; + s1 = peg$c51; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c48(); + s1 = peg$c53(); } s0 = s1; @@ -1633,7 +1750,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c35(s1, s2, s3); + s1 = peg$c40(s1, s2, s3); s0 = s1; } else { peg$currPos = s0; @@ -1663,44 +1780,44 @@ module.exports = (function() { var s0, s1; s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c49) { - s1 = peg$c49; + if (input.substr(peg$currPos, 2) === peg$c54) { + s1 = peg$c54; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c50); } + if (peg$silentFails === 0) { peg$fail(peg$c55); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c51(); + s1 = peg$c56(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c52) { - s1 = peg$c52; + if (input.substr(peg$currPos, 2) === peg$c57) { + s1 = peg$c57; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c53); } + if (peg$silentFails === 0) { peg$fail(peg$c58); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c54(); + s1 = peg$c59(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c55) { - s1 = peg$c55; + if (input.substr(peg$currPos, 2) === peg$c60) { + s1 = peg$c60; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c56); } + if (peg$silentFails === 0) { peg$fail(peg$c61); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c57(); + s1 = peg$c62(); } s0 = s1; } @@ -1714,17 +1831,17 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { s2 = peg$parseSpecialCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s2); + s1 = peg$c46(s2); s0 = s1; } else { peg$currPos = s0; @@ -1743,41 +1860,41 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c24) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c29) { s2 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c25); } + if (peg$silentFails === 0) { peg$fail(peg$c30); } } if (s2 === peg$FAILED) { - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c27) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c32) { s2 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c33); } } if (s2 === peg$FAILED) { - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c30) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c35) { s2 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c31); } + if (peg$silentFails === 0) { peg$fail(peg$c36); } } } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c58(s2); + s1 = peg$c63(s2); s0 = s1; } else { peg$currPos = s0; @@ -1808,12 +1925,12 @@ module.exports = (function() { function peg$parseSpecialCharacter() { var s0; - if (peg$c59.test(input.charAt(peg$currPos))) { + if (peg$c64.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c60); } + if (peg$silentFails === 0) { peg$fail(peg$c65); } } return s0; @@ -1823,58 +1940,58 @@ module.exports = (function() { var s0, s1; s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c61) { - s1 = peg$c61; + if (input.substr(peg$currPos, 2) === peg$c66) { + s1 = peg$c66; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c62); } + if (peg$silentFails === 0) { peg$fail(peg$c67); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c63(); + s1 = peg$c68(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c64) { - s1 = peg$c64; + if (input.substr(peg$currPos, 2) === peg$c69) { + s1 = peg$c69; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c65); } + if (peg$silentFails === 0) { peg$fail(peg$c70); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c66(); + s1 = peg$c71(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 60) { - s1 = peg$c67; + s1 = peg$c72; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c68); } + if (peg$silentFails === 0) { peg$fail(peg$c73); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c69(); + s1 = peg$c74(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 62) { - s1 = peg$c70; + s1 = peg$c75; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c71); } + if (peg$silentFails === 0) { peg$fail(peg$c76); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c72(); + s1 = peg$c77(); } s0 = s1; } @@ -1888,17 +2005,17 @@ module.exports = (function() { var s0, s1; peg$silentFails++; - if (peg$c74.test(input.charAt(peg$currPos))) { + if (peg$c79.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c75); } + if (peg$silentFails === 0) { peg$fail(peg$c80); } } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c73); } + if (peg$silentFails === 0) { peg$fail(peg$c78); } } return s0; @@ -1909,23 +2026,23 @@ module.exports = (function() { s0 = peg$currPos; peg$savedPos = peg$currPos; - s1 = peg$c76(); + s1 = peg$c81(); if (s1) { s1 = void 0; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 14) === peg$c77) { - s2 = peg$c77; + if (input.substr(peg$currPos, 14) === peg$c82) { + s2 = peg$c82; peg$currPos += 14; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c78); } + if (peg$silentFails === 0) { peg$fail(peg$c83); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c79(); + s1 = peg$c84(); s0 = s1; } else { peg$currPos = s0; @@ -1950,12 +2067,12 @@ module.exports = (function() { s2 = peg$parseSpace(); } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c80) { - s2 = peg$c80; + if (input.substr(peg$currPos, 2) === peg$c85) { + s2 = peg$c85; peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c81); } + if (peg$silentFails === 0) { peg$fail(peg$c86); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1966,7 +2083,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c82(); + s1 = peg$c87(); s0 = s1; } else { peg$currPos = s0; @@ -1995,12 +2112,12 @@ module.exports = (function() { s2 = peg$parseSpace(); } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c83) { - s2 = peg$c83; + if (input.substr(peg$currPos, 2) === peg$c88) { + s2 = peg$c88; peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c84); } + if (peg$silentFails === 0) { peg$fail(peg$c89); } } if (s2 !== peg$FAILED) { s3 = []; @@ -2011,7 +2128,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(); + s1 = peg$c90(); s0 = s1; } else { peg$currPos = s0; @@ -2028,15 +2145,15 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 43) { - s1 = peg$c86; + s1 = peg$c91; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c92); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(); + s1 = peg$c90(); } s0 = s1; } @@ -2049,29 +2166,29 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 45) { - s1 = peg$c88; + s1 = peg$c93; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c89); } + if (peg$silentFails === 0) { peg$fail(peg$c94); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c90(); + s1 = peg$c95(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 33) { - s1 = peg$c91; + s1 = peg$c96; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c92); } + if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c90(); + s1 = peg$c95(); } s0 = s1; } @@ -2107,11 +2224,11 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c12; + s3 = peg$c10; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s3 !== peg$FAILED) { s4 = []; @@ -2176,12 +2293,12 @@ module.exports = (function() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 8) === peg$c93) { - s1 = peg$c93; + if (input.substr(peg$currPos, 8) === peg$c98) { + s1 = peg$c98; peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c94); } + if (peg$silentFails === 0) { peg$fail(peg$c99); } } if (s1 !== peg$FAILED) { s2 = []; @@ -2192,11 +2309,11 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c12; + s3 = peg$c10; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s3 !== peg$FAILED) { s4 = []; @@ -2209,7 +2326,7 @@ module.exports = (function() { s5 = peg$parseLuceneLiteral(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c95(); + s1 = peg$c100(); s0 = s1; } else { peg$currPos = s0; @@ -2251,7 +2368,7 @@ module.exports = (function() { s3 = peg$parseLuceneLiteral(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c96(); + s1 = peg$c101(); s0 = s1; } else { peg$currPos = s0; @@ -2285,7 +2402,7 @@ module.exports = (function() { s6 = peg$parseLuceneRangeEnd(); if (s6 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c96(); + s1 = peg$c101(); s0 = s1; } else { peg$currPos = s0; @@ -2324,11 +2441,11 @@ module.exports = (function() { s2 = peg$parseLuceneUnquotedCharacter(); if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 42) { - s2 = peg$c46; + s2 = peg$c51; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } } while (s2 !== peg$FAILED) { @@ -2336,21 +2453,21 @@ module.exports = (function() { s2 = peg$parseLuceneUnquotedCharacter(); if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 42) { - s2 = peg$c46; + s2 = peg$c51; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 63) { - s2 = peg$c97; + s2 = peg$c102; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c98); } + if (peg$silentFails === 0) { peg$fail(peg$c103); } } if (s2 !== peg$FAILED) { s3 = []; @@ -2361,7 +2478,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c99(); + s1 = peg$c104(); s0 = s1; } else { peg$currPos = s0; @@ -2384,42 +2501,42 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 47) { - s1 = peg$c100; + s1 = peg$c105; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } if (s1 !== peg$FAILED) { s2 = []; - if (peg$c102.test(input.charAt(peg$currPos))) { + if (peg$c107.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c103); } + if (peg$silentFails === 0) { peg$fail(peg$c108); } } while (s3 !== peg$FAILED) { s2.push(s3); - if (peg$c102.test(input.charAt(peg$currPos))) { + if (peg$c107.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c103); } + if (peg$silentFails === 0) { peg$fail(peg$c108); } } } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 47) { - s3 = peg$c100; + s3 = peg$c105; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c104(); + s1 = peg$c109(); s0 = s1; } else { peg$currPos = s0; @@ -2444,34 +2561,34 @@ module.exports = (function() { s1 = peg$parseLuceneUnquotedLiteral(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 126) { - s2 = peg$c105; + s2 = peg$c110; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c106); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s2 !== peg$FAILED) { s3 = []; - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } while (s4 !== peg$FAILED) { s3.push(s4); - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c109(); + s1 = peg$c114(); s0 = s1; } else { peg$currPos = s0; @@ -2496,34 +2613,34 @@ module.exports = (function() { s1 = peg$parseQuotedString(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 126) { - s2 = peg$c105; + s2 = peg$c110; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c106); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s2 !== peg$FAILED) { s3 = []; - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } while (s4 !== peg$FAILED) { s3.push(s4); - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c110(); + s1 = peg$c115(); s0 = s1; } else { peg$currPos = s0; @@ -2548,34 +2665,34 @@ module.exports = (function() { s1 = peg$parseLuceneLiteral(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 94) { - s2 = peg$c111; + s2 = peg$c116; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c112); } + if (peg$silentFails === 0) { peg$fail(peg$c117); } } if (s2 !== peg$FAILED) { s3 = []; - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } while (s4 !== peg$FAILED) { s3.push(s4); - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c113(); + s1 = peg$c118(); s0 = s1; } else { peg$currPos = s0; @@ -2656,7 +2773,7 @@ module.exports = (function() { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } + if (peg$silentFails === 0) { peg$fail(peg$c50); } } if (s3 !== peg$FAILED) { s1 = [s1, s2, s3]; @@ -2707,17 +2824,17 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { s2 = peg$parseLuceneSpecialCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c114(); + s1 = peg$c119(); s0 = s1; } else { peg$currPos = s0; @@ -2735,51 +2852,51 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 43) { - s0 = peg$c86; + s0 = peg$c91; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c92); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 45) { - s0 = peg$c88; + s0 = peg$c93; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c89); } + if (peg$silentFails === 0) { peg$fail(peg$c94); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 61) { - s0 = peg$c115; + s0 = peg$c120; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c116); } + if (peg$silentFails === 0) { peg$fail(peg$c121); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 62) { - s0 = peg$c70; + s0 = peg$c75; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c71); } + if (peg$silentFails === 0) { peg$fail(peg$c76); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 60) { - s0 = peg$c67; + s0 = peg$c72; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c68); } + if (peg$silentFails === 0) { peg$fail(peg$c73); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 33) { - s0 = peg$c91; + s0 = peg$c96; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c92); } + if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 40) { @@ -2799,99 +2916,99 @@ module.exports = (function() { } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 123) { - s0 = peg$c117; + s0 = peg$c12; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c118); } + if (peg$silentFails === 0) { peg$fail(peg$c13); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 125) { - s0 = peg$c119; + s0 = peg$c14; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c120); } + if (peg$silentFails === 0) { peg$fail(peg$c15); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 91) { - s0 = peg$c121; + s0 = peg$c122; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c122); } + if (peg$silentFails === 0) { peg$fail(peg$c123); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 93) { - s0 = peg$c123; + s0 = peg$c124; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c124); } + if (peg$silentFails === 0) { peg$fail(peg$c125); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 94) { - s0 = peg$c111; + s0 = peg$c116; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c112); } + if (peg$silentFails === 0) { peg$fail(peg$c117); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s0 = peg$c33; + s0 = peg$c38; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 126) { - s0 = peg$c105; + s0 = peg$c110; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c106); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 42) { - s0 = peg$c46; + s0 = peg$c51; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 63) { - s0 = peg$c97; + s0 = peg$c102; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c98); } + if (peg$silentFails === 0) { peg$fail(peg$c103); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s0 = peg$c12; + s0 = peg$c10; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 92) { - s0 = peg$c37; + s0 = peg$c42; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 47) { - s0 = peg$c100; + s0 = peg$c105; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } } } @@ -2931,12 +3048,12 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c125) { - s2 = peg$c125; + if (input.substr(peg$currPos, 2) === peg$c126) { + s2 = peg$c126; peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c126); } + if (peg$silentFails === 0) { peg$fail(peg$c127); } } if (s2 !== peg$FAILED) { s3 = []; @@ -2972,19 +3089,19 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 91) { - s0 = peg$c121; + s0 = peg$c122; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c122); } + if (peg$silentFails === 0) { peg$fail(peg$c123); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 123) { - s0 = peg$c117; + s0 = peg$c12; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c118); } + if (peg$silentFails === 0) { peg$fail(peg$c13); } } } @@ -2995,19 +3112,19 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 93) { - s0 = peg$c123; + s0 = peg$c124; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c124); } + if (peg$silentFails === 0) { peg$fail(peg$c125); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 125) { - s0 = peg$c119; + s0 = peg$c14; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c120); } + if (peg$silentFails === 0) { peg$fail(peg$c15); } } } diff --git a/packages/kbn-es-query/src/kuery/ast/kuery.peg b/packages/kbn-es-query/src/kuery/ast/kuery.peg index ec8368d2a921de..389b9a82d2c76b 100644 --- a/packages/kbn-es-query/src/kuery/ast/kuery.peg +++ b/packages/kbn-es-query/src/kuery/ast/kuery.peg @@ -59,7 +59,26 @@ SubQuery } return query; } - / Expression + / NestedQuery + +NestedQuery + = field:Field Space* ':' Space* '{' Space* query:OrQuery trailing:OptionalSpace '}' { + if (query.type === 'cursor') { + return { + ...query, + nestedPath: query.nestedPath ? `${field.value}.${query.nestedPath}` : field.value, + } + }; + + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return buildFunctionNode('nested', [field, query]); + } + / Expression Expression = FieldRangeExpression @@ -272,7 +291,7 @@ Keyword = Or / And / Not SpecialCharacter - = [\\():<>"*] + = [\\():<>"*{}] RangeOperator = '<=' { return 'lte'; } diff --git a/packages/kbn-es-query/src/kuery/errors/index.test.js b/packages/kbn-es-query/src/kuery/errors/index.test.js index 91c90b2cce98b9..d8040e464b696b 100644 --- a/packages/kbn-es-query/src/kuery/errors/index.test.js +++ b/packages/kbn-es-query/src/kuery/errors/index.test.js @@ -25,7 +25,7 @@ describe('kql syntax errors', () => { it('should throw an error for a field query missing a value', () => { expect(() => { fromKueryExpression('response:'); - }).toThrow('Expected "(", value, whitespace but end of input found.\n' + + }).toThrow('Expected "(", "{", value, whitespace but end of input found.\n' + 'response:\n' + '---------^'); }); @@ -65,7 +65,7 @@ describe('kql syntax errors', () => { it('should throw an error for unbalanced quotes', () => { expect(() => { fromKueryExpression('foo:"ba '); - }).toThrow('Expected "(", value, whitespace but "\"" found.\n' + + }).toThrow('Expected "(", "{", value, whitespace but """ found.\n' + 'foo:"ba \n' + '----^'); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js b/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js index 0eaa69a3243e09..ee4cfab94e614d 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js @@ -72,6 +72,21 @@ describe('kuery functions', function () { expect(exists.toElasticsearchQuery) .withArgs(existsNode, indexPattern).to.throwException(/Exists query does not support scripted fields/); }); + + it('should use a provided nested context to create a full field name', function () { + const expected = { + exists: { field: 'nestedField.response' } + }; + + const existsNode = nodeTypes.function.buildNode('exists', 'response'); + const result = exists.toElasticsearchQuery( + existsNode, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(_.isEqual(expected, result)).to.be(true); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js index 2f8b784d6f6e8a..7afa0fcce1bfeb 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js @@ -102,6 +102,19 @@ describe('kuery functions', function () { expect(geoBoundingBox.toElasticsearchQuery) .withArgs(node, indexPattern).to.throwException(/Geo bounding box query does not support scripted fields/); }); + + it('should use a provided nested context to create a full field name', function () { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); + const result = geoBoundingBox.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.have.property('geo_bounding_box'); + expect(result.geo_bounding_box).to.have.property('nestedField.geo'); + }); + }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js index 1c6a2e0e47fde3..c1f2fae0bb3e1f 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js @@ -114,6 +114,18 @@ describe('kuery functions', function () { expect(geoPolygon.toElasticsearchQuery) .withArgs(node, indexPattern).to.throwException(/Geo polygon query does not support scripted fields/); }); + + it('should use a provided nested context to create a full field name', function () { + const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); + const result = geoPolygon.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.have.property('geo_polygon'); + expect(result.geo_polygon).to.have.property('nestedField.geo'); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/is.js b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js index 18652c9faccb8d..b2f3d7ec16a658 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/is.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js @@ -245,6 +245,66 @@ describe('kuery functions', function () { expect(result).to.eql(expected); }); + it('should use a provided nested context to create a full field name', function () { + const expected = { + bool: { + should: [ + { match: { 'nestedField.extension': 'jpg' } }, + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); + const result = is.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.eql(expected); + }); + + it('should support wildcard field names', function () { + const expected = { + bool: { + should: [ + { match: { extension: 'jpg' } }, + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg'); + const result = is.toElasticsearchQuery(node, indexPattern); + expect(result).to.eql(expected); + }); + + it('should automatically add a nested query when a wildcard field name covers a nested field', () => { + const expected = { + bool: { + should: [ + { + nested: { + path: 'nestedField.nestedChild', + query: { + match: { + 'nestedField.nestedChild.doublyNestedChild': 'foo' + } + }, + score_mode: 'none' + } + } + ], + minimum_should_match: 1 + } + }; + + + const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo'); + const result = is.toElasticsearchQuery(node, indexPattern); + expect(result).to.eql(expected); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js b/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js new file mode 100644 index 00000000000000..5ba73e485ddf1d --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js @@ -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 expect from '@kbn/expect'; +import * as nested from '../nested'; +import { nodeTypes } from '../../node_types'; +import * as ast from '../../ast'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; + +let indexPattern; + +const childNode = nodeTypes.function.buildNode('is', 'child', 'foo'); + +describe('kuery functions', function () { + describe('nested', function () { + + beforeEach(() => { + indexPattern = indexPatternResponse; + }); + + describe('buildNodeParams', function () { + + it('arguments should contain the unmodified child nodes', function () { + const result = nested.buildNodeParams('nestedField', childNode); + const { arguments: [ resultPath, resultChildNode ] } = result; + expect(ast.toElasticsearchQuery(resultPath)).to.be('nestedField'); + expect(resultChildNode).to.be(childNode); + }); + }); + + describe('toElasticsearchQuery', function () { + + it('should wrap subqueries in an ES nested query', function () { + const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode); + const result = nested.toElasticsearchQuery(node, indexPattern); + expect(result).to.only.have.keys('nested'); + expect(result.nested.path).to.be('nestedField'); + expect(result.nested.score_mode).to.be('none'); + }); + + it('should pass the nested path to subqueries so the full field name can be used', function () { + const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode); + const result = nested.toElasticsearchQuery(node, indexPattern); + const expectedSubQuery = ast.toElasticsearchQuery( + nodeTypes.function.buildNode('is', 'nestedField.child', 'foo') + ); + expect(result.nested.query).to.eql(expectedSubQuery); + }); + + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/range.js b/packages/kbn-es-query/src/kuery/functions/__tests__/range.js index 4f290206c8bfb7..2361e8bb667691 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/range.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/range.js @@ -181,6 +181,60 @@ describe('kuery functions', function () { expect(result).to.eql(expected); }); + it('should use a provided nested context to create a full field name', function () { + const expected = { + bool: { + should: [ + { + range: { + 'nestedField.bytes': { + gt: 1000, + lt: 8000 + } + } + } + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); + const result = range.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.eql(expected); + }); + + it('should automatically add a nested query when a wildcard field name covers a nested field', function () { + const expected = { + bool: { + should: [ + { + nested: { + path: 'nestedField.nestedChild', + query: { + range: { + 'nestedField.nestedChild.doublyNestedChild': { + gt: 1000, + lt: 8000 + } + } + }, + score_mode: 'none' + } + } + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('range', '*doublyNested*', { gt: 1000, lt: 8000 }); + const result = range.toElasticsearchQuery(node, indexPattern); + expect(result).to.eql(expected); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js new file mode 100644 index 00000000000000..dae15979a161cd --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js @@ -0,0 +1,88 @@ +/* + * 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 expect from '@kbn/expect'; +import { nodeTypes } from '../../../node_types'; +import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json'; +import { getFullFieldNameNode } from '../../utils/get_full_field_name_node'; + +let indexPattern; + +describe('getFullFieldNameNode', function () { + + beforeEach(() => { + indexPattern = indexPatternResponse; + }); + + it('should return unchanged name node if no nested path is passed in', () => { + const nameNode = nodeTypes.literal.buildNode('notNested'); + const result = getFullFieldNameNode(nameNode, indexPattern); + expect(result).to.eql(nameNode); + }); + + it('should add the nested path if it is valid according to the index pattern', () => { + const nameNode = nodeTypes.literal.buildNode('child'); + const result = getFullFieldNameNode(nameNode, indexPattern, 'nestedField'); + expect(result).to.eql(nodeTypes.literal.buildNode('nestedField.child')); + }); + + it('should throw an error if a path is provided for a non-nested field', () => { + const nameNode = nodeTypes.literal.buildNode('os'); + expect(getFullFieldNameNode) + .withArgs(nameNode, indexPattern, 'machine') + .to + .throwException(/machine.os is not a nested field but is in nested group "machine" in the KQL expression/); + }); + + it('should throw an error if a nested field is not passed with a path', () => { + const nameNode = nodeTypes.literal.buildNode('nestedField.child'); + expect(getFullFieldNameNode) + .withArgs(nameNode, indexPattern) + .to + .throwException(/nestedField.child is a nested field, but is not in a nested group in the KQL expression./); + }); + + it('should throw an error if a nested field is passed with the wrong path', () => { + const nameNode = nodeTypes.literal.buildNode('nestedChild.doublyNestedChild'); + expect(getFullFieldNameNode) + .withArgs(nameNode, indexPattern, 'nestedField') + .to + // eslint-disable-next-line max-len + .throwException(/Nested field nestedField.nestedChild.doublyNestedChild is being queried with the incorrect nested path. The correct path is nestedField.nestedChild/); + }); + + it('should skip error checking for wildcard names', () => { + const nameNode = nodeTypes.wildcard.buildNode('nested*'); + const result = getFullFieldNameNode(nameNode, indexPattern); + expect(result).to.eql(nameNode); + }); + + it('should skip error checking if no index pattern is passed in', () => { + const nameNode = nodeTypes.literal.buildNode('os'); + expect(getFullFieldNameNode) + .withArgs(nameNode, null, 'machine') + .to + .not + .throwException(); + + const result = getFullFieldNameNode(nameNode, null, 'machine'); + expect(result).to.eql(nodeTypes.literal.buildNode('machine.os')); + }); + +}); diff --git a/packages/kbn-es-query/src/kuery/functions/and.js b/packages/kbn-es-query/src/kuery/functions/and.js index 68e125ea4de59f..2650e83814b66f 100644 --- a/packages/kbn-es-query/src/kuery/functions/and.js +++ b/packages/kbn-es-query/src/kuery/functions/and.js @@ -25,13 +25,13 @@ export function buildNodeParams(children) { }; } -export function toElasticsearchQuery(node, indexPattern, config) { +export function toElasticsearchQuery(node, indexPattern, config, context) { const children = node.arguments || []; return { bool: { filter: children.map((child) => { - return ast.toElasticsearchQuery(child, indexPattern, config); + return ast.toElasticsearchQuery(child, indexPattern, config, context); }) } }; diff --git a/packages/kbn-es-query/src/kuery/functions/exists.js b/packages/kbn-es-query/src/kuery/functions/exists.js index 64810fc2ba7969..29940c8c2a4250 100644 --- a/packages/kbn-es-query/src/kuery/functions/exists.js +++ b/packages/kbn-es-query/src/kuery/functions/exists.js @@ -26,9 +26,10 @@ export function buildNodeParams(fieldName) { }; } -export function toElasticsearchQuery(node, indexPattern) { +export function toElasticsearchQuery(node, indexPattern = null, config, context = {}) { const { arguments: [ fieldNameArg ] } = node; - const fieldName = literal.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = { ...fieldNameArg, value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value }; + const fieldName = literal.toElasticsearchQuery(fullFieldNameArg); const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName); if (field && field.scripted) { diff --git a/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js index eeab146f6f698c..fd7e68e52e054e 100644 --- a/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js +++ b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js @@ -34,9 +34,10 @@ export function buildNodeParams(fieldName, params) { }; } -export function toElasticsearchQuery(node, indexPattern) { +export function toElasticsearchQuery(node, indexPattern, config, context = {}) { const [ fieldNameArg, ...args ] = node.arguments; - const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = { ...fieldNameArg, value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value }; + const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg); const field = _.get(indexPattern, 'fields', []).find(field => field.name === fieldName); const queryParams = args.reduce((acc, arg) => { const snakeArgName = _.snakeCase(arg.name); @@ -57,4 +58,3 @@ export function toElasticsearchQuery(node, indexPattern) { }, }; } - diff --git a/packages/kbn-es-query/src/kuery/functions/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/geo_polygon.js index 2e9f10071ea324..7756ae731ce6c5 100644 --- a/packages/kbn-es-query/src/kuery/functions/geo_polygon.js +++ b/packages/kbn-es-query/src/kuery/functions/geo_polygon.js @@ -33,12 +33,13 @@ export function buildNodeParams(fieldName, points) { }; } -export function toElasticsearchQuery(node, indexPattern) { +export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { const [ fieldNameArg, ...points ] = node.arguments; - const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = { ...fieldNameArg, value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value }; + const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg); const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName); const queryParams = { - points: points.map(ast.toElasticsearchQuery) + points: points.map((point) => { return ast.toElasticsearchQuery(point, indexPattern, config, context); }) }; if (field && field.scripted) { diff --git a/packages/kbn-es-query/src/kuery/functions/index.js b/packages/kbn-es-query/src/kuery/functions/index.js index 39b41f53018578..08b30f022f4318 100644 --- a/packages/kbn-es-query/src/kuery/functions/index.js +++ b/packages/kbn-es-query/src/kuery/functions/index.js @@ -25,6 +25,7 @@ import * as range from './range'; import * as exists from './exists'; import * as geoBoundingBox from './geo_bounding_box'; import * as geoPolygon from './geo_polygon'; +import * as nested from './nested'; export const functions = { is, @@ -35,4 +36,5 @@ export const functions = { exists, geoBoundingBox, geoPolygon, + nested, }; diff --git a/packages/kbn-es-query/src/kuery/functions/is.js b/packages/kbn-es-query/src/kuery/functions/is.js index 690f98b08ba827..63ade9e8793a7b 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.js +++ b/packages/kbn-es-query/src/kuery/functions/is.js @@ -21,9 +21,10 @@ import _ from 'lodash'; import * as ast from '../ast'; import * as literal from '../node_types/literal'; import * as wildcard from '../node_types/wildcard'; -import { getPhraseScript } from '../../filters'; +import { getPhraseScript } from '../../utils/filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings'; +import { getFullFieldNameNode } from './utils/get_full_field_name_node'; export function buildNodeParams(fieldName, value, isPhrase = false) { if (_.isUndefined(fieldName)) { @@ -40,12 +41,13 @@ export function buildNodeParams(fieldName, value, isPhrase = false) { }; } -export function toElasticsearchQuery(node, indexPattern = null, config = {}) { +export function toElasticsearchQuery(node, indexPattern = null, config = {}, context = {}) { const { arguments: [fieldNameArg, valueArg, isPhraseArg] } = node; - const fieldName = ast.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = getFullFieldNameNode(fieldNameArg, indexPattern, context.nested ? context.nested.path : undefined); + const fieldName = ast.toElasticsearchQuery(fullFieldNameArg); const value = !_.isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg; const type = isPhraseArg.value ? 'phrase' : 'best_fields'; - if (fieldNameArg.value === null) { + if (fullFieldNameArg.value === null) { if (valueArg.type === 'wildcard') { return { query_string: { @@ -63,7 +65,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { }; } - const fields = indexPattern ? getFields(fieldNameArg, indexPattern) : []; + const fields = indexPattern ? getFields(fullFieldNameArg, indexPattern) : []; // If no fields are found in the index pattern we send through the given field name as-is. We do this to preserve // the behaviour of lucene on dashboards where there are panels based on different index patterns that have different // fields. If a user queries on a field that exists in one pattern but not the other, the index pattern without the @@ -71,14 +73,14 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { // keep things familiar for now. if (fields && fields.length === 0) { fields.push({ - name: ast.toElasticsearchQuery(fieldNameArg), + name: ast.toElasticsearchQuery(fullFieldNameArg), scripted: false, }); } const isExistsQuery = valueArg.type === 'wildcard' && value === '*'; const isAllFieldsQuery = - (fieldNameArg.type === 'wildcard' && fieldName === '*') + (fullFieldNameArg.type === 'wildcard' && fieldName === '*') || (fields && indexPattern && fields.length === indexPattern.fields.length); const isMatchAllQuery = isExistsQuery && isAllFieldsQuery; @@ -87,6 +89,27 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { } const queries = fields.reduce((accumulator, field) => { + const wrapWithNestedQuery = (query) => { + // Wildcards can easily include nested and non-nested fields. There isn't a good way to let + // users handle this themselves so we automatically add nested queries in this scenario. + if ( + !(fullFieldNameArg.type === 'wildcard') + || !_.get(field, 'subType.nested') + || context.nested + ) { + return query; + } + else { + return { + nested: { + path: field.subType.nested.path, + query, + score_mode: 'none' + } + }; + } + }; + if (field.scripted) { // Exists queries don't make sense for scripted fields if (!isExistsQuery) { @@ -98,19 +121,19 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { } } else if (isExistsQuery) { - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ exists: { field: field.name } - }]; + })]; } else if (valueArg.type === 'wildcard') { - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ query_string: { fields: [field.name], query: wildcard.toQueryStringQuery(valueArg), } - }]; + })]; } /* If we detect that it's a date field and the user wants an exact date, we need to convert the query to both >= and <= the value provided to force a range query. This is because match and match_phrase queries do not accept a timezone parameter. @@ -118,7 +141,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { */ else if (field.type === 'date') { const timeZoneParam = config.dateFormatTZ ? { time_zone: getTimeZoneFromSettings(config.dateFormatTZ) } : {}; - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ range: { [field.name]: { gte: value, @@ -126,15 +149,15 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { ...timeZoneParam, }, } - }]; + })]; } else { const queryType = type === 'phrase' ? 'match_phrase' : 'match'; - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ [queryType]: { [field.name]: value } - }]; + })]; } }, []); @@ -146,4 +169,3 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { }; } - diff --git a/packages/kbn-es-query/src/kuery/functions/nested.js b/packages/kbn-es-query/src/kuery/functions/nested.js new file mode 100644 index 00000000000000..6237189e16311b --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/nested.js @@ -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 * as ast from '../ast'; +import * as literal from '../node_types/literal'; + +export function buildNodeParams(path, child) { + const pathNode = typeof path === 'string' ? ast.fromLiteralExpression(path) : literal.buildNode(path); + return { + arguments: [pathNode, child], + }; +} + +export function toElasticsearchQuery(node, indexPattern, config, context = {}) { + if (!indexPattern) { + throw new Error('Cannot use nested queries without an index pattern'); + } + + const [path, child] = node.arguments; + const stringPath = ast.toElasticsearchQuery(path); + const fullPath = context.nested && context.nested.path ? `${context.nested.path}.${stringPath}` : stringPath; + + return { + nested: { + path: fullPath, + query: ast.toElasticsearchQuery(child, indexPattern, config, { ...context, nested: { path: fullPath } }), + score_mode: 'none', + }, + }; +} diff --git a/packages/kbn-es-query/src/kuery/functions/not.js b/packages/kbn-es-query/src/kuery/functions/not.js index d3ab14df16bcb4..8a4b8c6060109d 100644 --- a/packages/kbn-es-query/src/kuery/functions/not.js +++ b/packages/kbn-es-query/src/kuery/functions/not.js @@ -25,13 +25,12 @@ export function buildNodeParams(child) { }; } -export function toElasticsearchQuery(node, indexPattern, config) { +export function toElasticsearchQuery(node, indexPattern, config, context) { const [ argument ] = node.arguments; return { bool: { - must_not: ast.toElasticsearchQuery(argument, indexPattern, config) + must_not: ast.toElasticsearchQuery(argument, indexPattern, config, context) } }; } - diff --git a/packages/kbn-es-query/src/kuery/functions/or.js b/packages/kbn-es-query/src/kuery/functions/or.js index 918d46a6691de4..9b27ac9151801e 100644 --- a/packages/kbn-es-query/src/kuery/functions/or.js +++ b/packages/kbn-es-query/src/kuery/functions/or.js @@ -25,13 +25,13 @@ export function buildNodeParams(children) { }; } -export function toElasticsearchQuery(node, indexPattern, config) { +export function toElasticsearchQuery(node, indexPattern, config, context) { const children = node.arguments || []; return { bool: { should: children.map((child) => { - return ast.toElasticsearchQuery(child, indexPattern, config); + return ast.toElasticsearchQuery(child, indexPattern, config, context); }), minimum_should_match: 1, }, diff --git a/packages/kbn-es-query/src/kuery/functions/range.js b/packages/kbn-es-query/src/kuery/functions/range.js index df77baf4b02083..f7719998ad5240 100644 --- a/packages/kbn-es-query/src/kuery/functions/range.js +++ b/packages/kbn-es-query/src/kuery/functions/range.js @@ -20,9 +20,10 @@ import _ from 'lodash'; import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { getRangeScript } from '../../filters'; +import { getRangeScript } from '../../utils/filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings'; +import { getFullFieldNameNode } from './utils/get_full_field_name_node'; export function buildNodeParams(fieldName, params) { params = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format'); @@ -36,9 +37,10 @@ export function buildNodeParams(fieldName, params) { }; } -export function toElasticsearchQuery(node, indexPattern = null, config = {}) { +export function toElasticsearchQuery(node, indexPattern = null, config = {}, context = {}) { const [ fieldNameArg, ...args ] = node.arguments; - const fields = indexPattern ? getFields(fieldNameArg, indexPattern) : []; + const fullFieldNameArg = getFullFieldNameNode(fieldNameArg, indexPattern, context.nested ? context.nested.path : undefined); + const fields = indexPattern ? getFields(fullFieldNameArg, indexPattern) : []; const namedArgs = extractArguments(args); const queryParams = _.mapValues(namedArgs, ast.toElasticsearchQuery); @@ -49,13 +51,34 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { // keep things familiar for now. if (fields && fields.length === 0) { fields.push({ - name: ast.toElasticsearchQuery(fieldNameArg), + name: ast.toElasticsearchQuery(fullFieldNameArg), scripted: false, }); } const queries = fields.map((field) => { + const wrapWithNestedQuery = (query) => { + // Wildcards can easily include nested and non-nested fields. There isn't a good way to let + // users handle this themselves so we automatically add nested queries in this scenario. + if ( + !fullFieldNameArg.type === 'wildcard' + || !_.get(field, 'subType.nested') + || context.nested + ) { + return query; + } + else { + return { + nested: { + path: field.subType.nested.path, + query, + score_mode: 'none' + } + }; + } + }; + if (field.scripted) { return { script: getRangeScript(field, queryParams), @@ -63,20 +86,20 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { } else if (field.type === 'date') { const timeZoneParam = config.dateFormatTZ ? { time_zone: getTimeZoneFromSettings(config.dateFormatTZ) } : {}; - return { + return wrapWithNestedQuery({ range: { [field.name]: { ...queryParams, ...timeZoneParam, } } - }; + }); } - return { + return wrapWithNestedQuery({ range: { [field.name]: queryParams } - }; + }); }); return { diff --git a/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js new file mode 100644 index 00000000000000..07540344b79553 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js @@ -0,0 +1,63 @@ +/* + * 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 { getFields } from './get_fields'; + +export function getFullFieldNameNode(rootNameNode, indexPattern, nestedPath) { + const fullFieldNameNode = { + ...rootNameNode, + value: nestedPath ? `${nestedPath}.${rootNameNode.value}` : rootNameNode.value + }; + + // Wildcards can easily include nested and non-nested fields. There isn't a good way to let + // users handle this themselves so we automatically add nested queries in this scenario and skip the + // error checking below. + if (!indexPattern || (fullFieldNameNode.type === 'wildcard' && !nestedPath)) { + return fullFieldNameNode; + } + const fields = getFields(fullFieldNameNode, indexPattern); + + const errors = fields.reduce((acc, field) => { + const nestedPathFromField = field.subType && field.subType.nested ? field.subType.nested.path : undefined; + + if (nestedPath && !nestedPathFromField) { + return [...acc, `${field.name} is not a nested field but is in nested group "${nestedPath}" in the KQL expression.`]; + } + + if (nestedPathFromField && !nestedPath) { + return [...acc, `${field.name} is a nested field, but is not in a nested group in the KQL expression.`]; + } + + if (nestedPathFromField !== nestedPath) { + return [ + ...acc, + `Nested field ${field.name} is being queried with the incorrect nested path. The correct path is ${field.subType.nested.path}.` + ]; + } + + return acc; + }, []); + + if (errors.length > 0) { + throw new Error(errors.join('\n')); + } + + return fullFieldNameNode; +} diff --git a/packages/kbn-es-query/src/kuery/node_types/function.js b/packages/kbn-es-query/src/kuery/node_types/function.js index 6b10bb1f704c85..d54b7de6cd8ea5 100644 --- a/packages/kbn-es-query/src/kuery/node_types/function.js +++ b/packages/kbn-es-query/src/kuery/node_types/function.js @@ -47,8 +47,7 @@ export function buildNodeWithArgumentNodes(functionName, argumentNodes) { }; } -export function toElasticsearchQuery(node, indexPattern, config = {}) { +export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { const kueryFunction = functions[node.function]; - return kueryFunction.toElasticsearchQuery(node, indexPattern, config); + return kueryFunction.toElasticsearchQuery(node, indexPattern, config, context); } - diff --git a/packages/kbn-es-query/src/kuery/node_types/index.d.ts b/packages/kbn-es-query/src/kuery/node_types/index.d.ts index 0d1f2c28e39f08..daf8032f9fe0ef 100644 --- a/packages/kbn-es-query/src/kuery/node_types/index.d.ts +++ b/packages/kbn-es-query/src/kuery/node_types/index.d.ts @@ -31,7 +31,8 @@ type FunctionName = | 'range' | 'exists' | 'geoBoundingBox' - | 'geoPolygon'; + | 'geoPolygon' + | 'nested'; interface FunctionTypeBuildNode { type: 'function'; diff --git a/packages/kbn-es-query/src/utils/filters.js b/packages/kbn-es-query/src/utils/filters.js new file mode 100644 index 00000000000000..6e4f5c342688ce --- /dev/null +++ b/packages/kbn-es-query/src/utils/filters.js @@ -0,0 +1,133 @@ +/* + * 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 { pick, get, reduce, map } from 'lodash'; + +/** @deprecated + * @see src/plugins/data/public/es_query/filters/phrase_filter.ts + * Code was already moved into src/plugins/data/public. + * This method will be removed after moving 'es_query' into new platform + * */ +export const getConvertedValueForField = (field, value) => { + if (typeof value !== 'boolean' && field.type === 'boolean') { + if ([1, 'true'].includes(value)) { + return true; + } else if ([0, 'false'].includes(value)) { + return false; + } else { + throw new Error(`${value} is not a valid boolean value for boolean field ${field.name}`); + } + } + return value; +}; + +/** @deprecated + * @see src/plugins/data/public/es_query/filters/phrase_filter.ts + * Code was already moved into src/plugins/data/public. + * This method will be removed after moving 'es_query' into new platform + * */ +export const buildInlineScriptForPhraseFilter = (scriptedField) => { + // We must wrap painless scripts in a lambda in case they're more than a simple expression + if (scriptedField.lang === 'painless') { + return ( + `boolean compare(Supplier s, def v) {return s.get() == v;}` + + `compare(() -> { ${scriptedField.script} }, params.value);` + ); + } else { + return `(${scriptedField.script}) == value`; + } +}; + +/** @deprecated + * @see src/plugins/data/public/es_query/filters/phrase_filter.ts + * Code was already moved into src/plugins/data/public. + * This method will be removed after moving 'es_query' into new platform + * */ +export function getPhraseScript(field, value) { + const convertedValue = getConvertedValueForField(field, value); + const script = buildInlineScriptForPhraseFilter(field); + + return { + script: { + source: script, + lang: field.lang, + params: { + value: convertedValue, + }, + }, + }; +} + +/** @deprecated + * @see src/plugins/data/public/es_query/filters/range_filter.ts + * Code was already moved into src/plugins/data/public. + * This method will be removed after moving 'kuery' into new platform + * */ +export function getRangeScript(field, params) { + const operators = { + gt: '>', + gte: '>=', + lte: '<=', + lt: '<', + }; + const comparators = { + gt: 'boolean gt(Supplier s, def v) {return s.get() > v}', + gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}', + lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}', + lt: 'boolean lt(Supplier s, def v) {return s.get() < v}', + }; + + const dateComparators = { + gt: 'boolean gt(Supplier s, def v) {return s.get().toInstant().isAfter(Instant.parse(v))}', + gte: 'boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))}', + lte: 'boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}', + lt: 'boolean lt(Supplier s, def v) {return s.get().toInstant().isBefore(Instant.parse(v))}', + }; + + const knownParams = pick(params, (val, key) => { + return key in operators; + }); + let script = map(knownParams, (val, key) => { + return '(' + field.script + ')' + get(operators, key) + key; + }).join(' && '); + + // We must wrap painless scripts in a lambda in case they're more than a simple expression + if (field.lang === 'painless') { + const comp = field.type === 'date' ? dateComparators : comparators; + const currentComparators = reduce( + knownParams, + (acc, val, key) => acc.concat(get(comp, key)), + [] + ).join(' '); + + const comparisons = map(knownParams, (val, key) => { + return `${key}(() -> { ${field.script} }, params.${key})`; + }).join(' && '); + + script = `${currentComparators}${comparisons}`; + } + + return { + script: { + source: script, + params: knownParams, + lang: field.lang, + }, + }; +} diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index 5280c671450fa5..cb501dab3ddb75 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -10,8 +10,8 @@ "abort-controller": "^2.0.3", "chalk": "^2.4.2", "dedent": "^0.7.0", - "del": "^4.1.1", - "execa": "^1.0.0", + "del": "^5.1.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "glob": "^7.1.2", "node-fetch": "^2.6.0", diff --git a/packages/kbn-eslint-plugin-eslint/rules/__tests__/files/no_restricted_paths/server/index_patterns/index.js b/packages/kbn-eslint-plugin-eslint/rules/__tests__/files/no_restricted_paths/server/index_patterns/index.js new file mode 100644 index 00000000000000..d15de7d98a9e0c --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/__tests__/files/no_restricted_paths/server/index_patterns/index.js @@ -0,0 +1 @@ +/* eslint-disable */ diff --git a/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js b/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js index f393a867d95e0d..577be820ccc7bc 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js +++ b/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js @@ -172,6 +172,27 @@ ruleTester.run('@kbn/eslint/no-restricted-paths', rule, { }, ], }, + + { + // Check if dirs that start with 'index' work correctly. + code: 'import { X } from "./index_patterns"', + filename: path.join(__dirname, './files/no_restricted_paths/server/b.js'), + options: [ + { + basePath: __dirname, + zones: [ + { + target: ['files/no_restricted_paths/(public|server)/**/*'], + from: [ + 'files/no_restricted_paths/server/**/*', + '!files/no_restricted_paths/server/index.{ts,tsx}', + ], + allowSameFolder: true, + }, + ], + }, + ], + }, ], invalid: [ @@ -369,5 +390,34 @@ ruleTester.run('@kbn/eslint/no-restricted-paths', rule, { }, ], }, + + { + // Don't use index*. + // It won't work with dirs that start with 'index'. + code: 'import { X } from "./index_patterns"', + filename: path.join(__dirname, './files/no_restricted_paths/server/b.js'), + options: [ + { + basePath: __dirname, + zones: [ + { + target: ['files/no_restricted_paths/(public|server)/**/*'], + from: [ + 'files/no_restricted_paths/server/**/*', + '!files/no_restricted_paths/server/index*', + ], + allowSameFolder: true, + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "./index_patterns" imported in restricted zone.', + line: 1, + column: 19, + }, + ], + }, ], }); diff --git a/packages/kbn-expect/expect.js b/packages/kbn-expect/expect.js index 8dc8af4cab8949..bc75d19d2ab1fa 100644 --- a/packages/kbn-expect/expect.js +++ b/packages/kbn-expect/expect.js @@ -98,6 +98,9 @@ Assertion.prototype.assert = function (truth, msg, error, expected) { if (!ok) { err = new Error(msg.call(this)); + if (this.customMsg) { + err.message = this.customMsg; + } if (arguments.length > 3) { err.actual = this.obj; err.expected = expected; @@ -217,7 +220,10 @@ Assertion.prototype.empty = function () { */ Assertion.prototype.be = -Assertion.prototype.equal = function (obj) { +Assertion.prototype.equal = function (obj, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( obj === this.obj , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } @@ -231,7 +237,10 @@ Assertion.prototype.equal = function (obj) { * @api public */ -Assertion.prototype.eql = function (obj) { +Assertion.prototype.eql = function (obj, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( expect.eql(this.obj, obj) , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } @@ -248,7 +257,10 @@ Assertion.prototype.eql = function (obj) { * @api public */ -Assertion.prototype.within = function (start, finish) { +Assertion.prototype.within = function (start, finish, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } var range = start + '..' + finish; this.assert( this.obj >= start && this.obj <= finish @@ -298,7 +310,10 @@ Assertion.prototype.an = function (type) { */ Assertion.prototype.greaterThan = -Assertion.prototype.above = function (n) { +Assertion.prototype.above = function (n, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( this.obj > n , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } @@ -314,7 +329,10 @@ Assertion.prototype.above = function (n) { */ Assertion.prototype.lessThan = -Assertion.prototype.below = function (n) { +Assertion.prototype.below = function (n, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( this.obj < n , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } @@ -329,7 +347,10 @@ Assertion.prototype.below = function (n) { * @api public */ -Assertion.prototype.match = function (regexp) { +Assertion.prototype.match = function (regexp, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( regexp.exec(this.obj) , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } @@ -344,7 +365,10 @@ Assertion.prototype.match = function (regexp) { * @api public */ -Assertion.prototype.length = function (n) { +Assertion.prototype.length = function (n, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } expect(this.obj).to.have.property('length'); var len = this.obj.length; this.assert( @@ -410,7 +434,10 @@ Assertion.prototype.property = function (name, val) { */ Assertion.prototype.string = -Assertion.prototype.contain = function (obj) { +Assertion.prototype.contain = function (obj, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } if ('string' == typeof this.obj) { this.assert( ~this.obj.indexOf(obj) diff --git a/packages/kbn-expect/expect.js.d.ts b/packages/kbn-expect/expect.js.d.ts index 2062dea686500f..b957a1f9ab1099 100644 --- a/packages/kbn-expect/expect.js.d.ts +++ b/packages/kbn-expect/expect.js.d.ts @@ -59,12 +59,12 @@ interface Assertion { /** * Checks if the obj exactly equals another. */ - equal(obj: any): Assertion; + equal(obj: any, msg?: string): Assertion; /** * Checks if the obj sortof equals another. */ - eql(obj: any): Assertion; + eql(obj: any, msg?: string): Assertion; /** * Assert within start to finish (inclusive). @@ -72,7 +72,7 @@ interface Assertion { * @param start * @param finish */ - within(start: number, finish: number): Assertion; + within(start: number, finish: number, msg?: string): Assertion; /** * Assert typeof. @@ -87,36 +87,36 @@ interface Assertion { /** * Assert numeric value above n. */ - greaterThan(n: number): Assertion; + greaterThan(n: number, msg?: string): Assertion; /** * Assert numeric value above n. */ - above(n: number): Assertion; + above(n: number, msg?: string): Assertion; /** * Assert numeric value below n. */ - lessThan(n: number): Assertion; + lessThan(n: number, msg?: string): Assertion; /** * Assert numeric value below n. */ - below(n: number): Assertion; + below(n: number, msg?: string): Assertion; /** * Assert string value matches regexp. * * @param regexp */ - match(regexp: RegExp): Assertion; + match(regexp: RegExp, msg?: string): Assertion; /** * Assert property "length" exists and has value of n. * * @param n */ - length(n: number): Assertion; + length(n: number, msg?: string): Assertion; /** * Assert property name exists, with optional val. @@ -129,14 +129,14 @@ interface Assertion { /** * Assert that string contains str. */ - contain(str: string): Assertion; - string(str: string): Assertion; + contain(str: string, msg?: string): Assertion; + string(str: string, msg?: string): Assertion; /** * Assert that the array contains obj. */ - contain(obj: any): Assertion; - string(obj: any): Assertion; + contain(obj: any, msg?: string): Assertion; + string(obj: any, msg?: string): Assertion; /** * Assert exact keys or inclusion of keys by using the `.own` modifier. diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index dff8a3f2f4b783..8a88626bffbe85 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -18,7 +18,7 @@ "@kbn/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", "@types/react-intl": "^2.3.15", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "supports-color": "^7.0.0", "typescript": "3.5.3" diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index f2aa2e1ced9cd0..27ef70d8718569 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -24,7 +24,7 @@ "babel-loader": "^8.0.6", "copy-webpack-plugin": "^5.0.4", "css-loader": "2.1.1", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "pegjs": "0.10.0", "sass-loader": "^7.3.1", diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index 74ccbd8e758b56..ac98a0e675fb18 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -6,7 +6,7 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "execa": "^1.0.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", diff --git a/packages/kbn-plugin-generator/sao_template/template/public/app.js b/packages/kbn-plugin-generator/sao_template/template/public/app.js index 031878d70f9f3e..37a7c37e916a0f 100755 --- a/packages/kbn-plugin-generator/sao_template/template/public/app.js +++ b/packages/kbn-plugin-generator/sao_template/template/public/app.js @@ -6,7 +6,6 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; <%_ } _%> -import 'ui/autoload/styles'; import { Main } from './components/main'; const app = uiModules.get('apps/<%= camelCase(name) %>'); diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 215963c0769bb5..68af0aa791c8e0 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -16,8 +16,8 @@ "@babel/core": "^7.5.5", "argv-split": "^2.0.1", "commander": "^3.0.0", - "del": "^4.1.1", - "execa": "^1.0.0", + "del": "^5.1.0", + "execa": "^3.2.0", "globby": "^8.0.1", "gulp-babel": "^8.0.0", "gulp-rename": "1.4.0", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index f52c194cfee6af..bbe12a93c241f7 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(404); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(484); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); @@ -105,10 +105,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(54); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Project", function() { return _utils_project__WEBPACK_IMPORTED_MODULE_3__["Project"]; }); -/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(163); +/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(171); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__["copyWorkspacePackages"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(172); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -152,7 +152,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(394); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(475); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(34); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -2502,9 +2502,9 @@ module.exports = require("path"); __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(18); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(165); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(192); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(193); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(173); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(272); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(273); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -4415,7 +4415,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(53); /* harmony import */ var _project__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(54); -/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(163); +/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(171); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -19082,9 +19082,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(122); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(150); +/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(158); /* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(log_symbols__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(155); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(163); /* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } @@ -19124,7 +19124,8 @@ function generateColors() { function spawn(command, args, opts) { return execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ - stdio: 'inherit' + stdio: 'inherit', + preferLocal: true }, opts)); } const nextColor = generateColors(); @@ -19132,7 +19133,8 @@ function spawnStreaming(command, args, opts, { prefix }) { const spawned = execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ - stdio: ['ignore', 'pipe', 'pipe'] + stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true }, opts)); const color = nextColor(); const prefixedStdout = strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default()({ @@ -19156,363 +19158,258 @@ function spawnStreaming(command, args, opts, { const path = __webpack_require__(16); const childProcess = __webpack_require__(123); const crossSpawn = __webpack_require__(124); -const stripEof = __webpack_require__(139); -const npmRunPath = __webpack_require__(140); -const isStream = __webpack_require__(142); -const _getStream = __webpack_require__(143); -const pFinally = __webpack_require__(147); -const onExit = __webpack_require__(110); -const errname = __webpack_require__(148); -const stdio = __webpack_require__(149); - -const TEN_MEGABYTES = 1000 * 1000 * 10; - -function handleArgs(cmd, args, opts) { - let parsed; - - opts = Object.assign({ - extendEnv: true, - env: {} - }, opts); - - if (opts.extendEnv) { - opts.env = Object.assign({}, process.env, opts.env); +const stripFinalNewline = __webpack_require__(137); +const npmRunPath = __webpack_require__(138); +const onetime = __webpack_require__(139); +const makeError = __webpack_require__(141); +const normalizeStdio = __webpack_require__(146); +const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(147); +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(149); +const {mergePromise, getSpawnedPromise} = __webpack_require__(156); +const {joinCommand, parseCommand} = __webpack_require__(157); + +const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; + +const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => { + const env = extendEnv ? {...process.env, ...envOption} : envOption; + + if (preferLocal) { + return npmRunPath.env({env, cwd: localDir, execPath}); } - if (opts.__winShell === true) { - delete opts.__winShell; - parsed = { - command: cmd, - args, - options: opts, - file: cmd, - original: { - cmd, - args - } - }; - } else { - parsed = crossSpawn._parse(cmd, args, opts); - } + return env; +}; - opts = Object.assign({ - maxBuffer: TEN_MEGABYTES, +const handleArgs = (file, args, options = {}) => { + const parsed = crossSpawn._parse(file, args, options); + file = parsed.command; + args = parsed.args; + options = parsed.options; + + options = { + maxBuffer: DEFAULT_MAX_BUFFER, buffer: true, - stripEof: true, - preferLocal: true, - localDir: parsed.options.cwd || process.cwd(), + stripFinalNewline: true, + extendEnv: true, + preferLocal: false, + localDir: options.cwd || process.cwd(), + execPath: process.execPath, encoding: 'utf8', reject: true, - cleanup: true - }, parsed.options); - - opts.stdio = stdio(opts); - - if (opts.preferLocal) { - opts.env = npmRunPath.env(Object.assign({}, opts, {cwd: opts.localDir})); - } - - if (opts.detached) { - // #115 - opts.cleanup = false; - } - - if (process.platform === 'win32' && path.basename(parsed.command) === 'cmd.exe') { - // #116 - parsed.args.unshift('/q'); - } - - return { - cmd: parsed.command, - args: parsed.args, - opts, - parsed + cleanup: true, + all: false, + ...options, + windowsHide: true }; -} - -function handleInput(spawned, input) { - if (input === null || input === undefined) { - return; - } - - if (isStream(input)) { - input.pipe(spawned.stdin); - } else { - spawned.stdin.end(input); - } -} - -function handleOutput(opts, val) { - if (val && opts.stripEof) { - val = stripEof(val); - } - - return val; -} - -function handleShell(fn, cmd, opts) { - let file = '/bin/sh'; - let args = ['-c', cmd]; - - opts = Object.assign({}, opts); - - if (process.platform === 'win32') { - opts.__winShell = true; - file = process.env.comspec || 'cmd.exe'; - args = ['/s', '/c', `"${cmd}"`]; - opts.windowsVerbatimArguments = true; - } - - if (opts.shell) { - file = opts.shell; - delete opts.shell; - } - - return fn(file, args, opts); -} -function getStream(process, stream, {encoding, buffer, maxBuffer}) { - if (!process[stream]) { - return null; - } + options.env = getEnv(options); - let ret; + options.stdio = normalizeStdio(options); - if (!buffer) { - // TODO: Use `ret = util.promisify(stream.finished)(process[stream]);` when targeting Node.js 10 - ret = new Promise((resolve, reject) => { - process[stream] - .once('end', resolve) - .once('error', reject); - }); - } else if (encoding) { - ret = _getStream(process[stream], { - encoding, - maxBuffer - }); - } else { - ret = _getStream.buffer(process[stream], {maxBuffer}); + if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') { + // #116 + args.unshift('/q'); } - return ret.catch(err => { - err.stream = stream; - err.message = `${stream} ${err.message}`; - throw err; - }); -} - -function makeError(result, options) { - const {stdout, stderr} = result; - - let err = result.error; - const {code, signal} = result; - - const {parsed, joinedCmd} = options; - const timedOut = options.timedOut || false; - - if (!err) { - let output = ''; - - if (Array.isArray(parsed.opts.stdio)) { - if (parsed.opts.stdio[2] !== 'inherit') { - output += output.length > 0 ? stderr : `\n${stderr}`; - } - - if (parsed.opts.stdio[1] !== 'inherit') { - output += `\n${stdout}`; - } - } else if (parsed.opts.stdio !== 'inherit') { - output = `\n${stderr}${stdout}`; - } + return {file, args, options, parsed}; +}; - err = new Error(`Command failed: ${joinedCmd}${output}`); - err.code = code < 0 ? errname(code) : code; +const handleOutput = (options, value, error) => { + if (typeof value !== 'string' && !Buffer.isBuffer(value)) { + // When `execa.sync()` errors, we normalize it to '' to mimic `execa()` + return error === undefined ? undefined : ''; } - err.stdout = stdout; - err.stderr = stderr; - err.failed = true; - err.signal = signal || null; - err.cmd = joinedCmd; - err.timedOut = timedOut; - - return err; -} - -function joinCmd(cmd, args) { - let joinedCmd = cmd; - - if (Array.isArray(args) && args.length > 0) { - joinedCmd += ' ' + args.join(' '); + if (options.stripFinalNewline) { + return stripFinalNewline(value); } - return joinedCmd; -} + return value; +}; -module.exports = (cmd, args, opts) => { - const parsed = handleArgs(cmd, args, opts); - const {encoding, buffer, maxBuffer} = parsed.opts; - const joinedCmd = joinCmd(cmd, args); +const execa = (file, args, options) => { + const parsed = handleArgs(file, args, options); + const command = joinCommand(file, args); let spawned; try { - spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts); - } catch (err) { - return Promise.reject(err); - } - - let removeExitHandler; - if (parsed.opts.cleanup) { - removeExitHandler = onExit(() => { - spawned.kill(); - }); - } - - let timeoutId = null; - let timedOut = false; - - const cleanup = () => { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - - if (removeExitHandler) { - removeExitHandler(); - } - }; - - if (parsed.opts.timeout > 0) { - timeoutId = setTimeout(() => { - timeoutId = null; - timedOut = true; - spawned.kill(parsed.opts.killSignal); - }, parsed.opts.timeout); - } - - const processDone = new Promise(resolve => { - spawned.on('exit', (code, signal) => { - cleanup(); - resolve({code, signal}); - }); - - spawned.on('error', err => { - cleanup(); - resolve({error: err}); - }); - - if (spawned.stdin) { - spawned.stdin.on('error', err => { - cleanup(); - resolve({error: err}); - }); - } - }); - - function destroy() { - if (spawned.stdout) { - spawned.stdout.destroy(); - } - - if (spawned.stderr) { - spawned.stderr.destroy(); - } + spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options); + } catch (error) { + // Ensure the returned error is always both a promise and a child process + const dummySpawned = new childProcess.ChildProcess(); + const errorPromise = Promise.reject(makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + parsed, + timedOut: false, + isCanceled: false, + killed: false + })); + return mergePromise(dummySpawned, errorPromise); } - const handlePromise = () => pFinally(Promise.all([ - processDone, - getStream(spawned, 'stdout', {encoding, buffer, maxBuffer}), - getStream(spawned, 'stderr', {encoding, buffer, maxBuffer}) - ]).then(arr => { - const result = arr[0]; - result.stdout = arr[1]; - result.stderr = arr[2]; - - if (result.error || result.code !== 0 || result.signal !== null) { - const err = makeError(result, { - joinedCmd, + const spawnedPromise = getSpawnedPromise(spawned); + const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise); + const processDone = setExitHandler(spawned, parsed.options, timedPromise); + + const context = {isCanceled: false}; + + spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned)); + spawned.cancel = spawnedCancel.bind(null, spawned, context); + + const handlePromise = async () => { + const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone); + const stdout = handleOutput(parsed.options, stdoutResult); + const stderr = handleOutput(parsed.options, stderrResult); + const all = handleOutput(parsed.options, allResult); + + if (error || exitCode !== 0 || signal !== null) { + const returnedError = makeError({ + error, + exitCode, + signal, + stdout, + stderr, + all, + command, parsed, - timedOut + timedOut, + isCanceled: context.isCanceled, + killed: spawned.killed }); - // TODO: missing some timeout logic for killed - // https://github.com/nodejs/node/blob/master/lib/child_process.js#L203 - // err.killed = spawned.killed || killed; - err.killed = err.killed || spawned.killed; - - if (!parsed.opts.reject) { - return err; + if (!parsed.options.reject) { + return returnedError; } - throw err; + throw returnedError; } return { - stdout: handleOutput(parsed.opts, result.stdout), - stderr: handleOutput(parsed.opts, result.stderr), - code: 0, + command, + exitCode: 0, + stdout, + stderr, + all, failed: false, - killed: false, - signal: null, - cmd: joinedCmd, - timedOut: false + timedOut: false, + isCanceled: false, + killed: false }; - }), destroy); + }; + + const handlePromiseOnce = onetime(handlePromise); crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed); - handleInput(spawned, parsed.opts.input); + handleInput(spawned, parsed.options.input); - spawned.then = (onfulfilled, onrejected) => handlePromise().then(onfulfilled, onrejected); - spawned.catch = onrejected => handlePromise().catch(onrejected); + spawned.all = makeAllStream(spawned, parsed.options); - return spawned; + return mergePromise(spawned, handlePromiseOnce); }; -// TODO: set `stderr: 'ignore'` when that option is implemented -module.exports.stdout = (...args) => module.exports(...args).then(x => x.stdout); +module.exports = execa; -// TODO: set `stdout: 'ignore'` when that option is implemented -module.exports.stderr = (...args) => module.exports(...args).then(x => x.stderr); +module.exports.sync = (file, args, options) => { + const parsed = handleArgs(file, args, options); + const command = joinCommand(file, args); -module.exports.shell = (cmd, opts) => handleShell(module.exports, cmd, opts); + validateInputSync(parsed.options); -module.exports.sync = (cmd, args, opts) => { - const parsed = handleArgs(cmd, args, opts); - const joinedCmd = joinCmd(cmd, args); - - if (isStream(parsed.opts.input)) { - throw new TypeError('The `input` option cannot be a stream in sync mode'); + let result; + try { + result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options); + } catch (error) { + throw makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + parsed, + timedOut: false, + isCanceled: false, + killed: false + }); } - const result = childProcess.spawnSync(parsed.cmd, parsed.args, parsed.opts); - result.code = result.status; + const stdout = handleOutput(parsed.options, result.stdout, result.error); + const stderr = handleOutput(parsed.options, result.stderr, result.error); if (result.error || result.status !== 0 || result.signal !== null) { - const err = makeError(result, { - joinedCmd, - parsed + const error = makeError({ + stdout, + stderr, + error: result.error, + signal: result.signal, + exitCode: result.status, + command, + parsed, + timedOut: result.error && result.error.code === 'ETIMEDOUT', + isCanceled: false, + killed: result.signal !== null }); - if (!parsed.opts.reject) { - return err; + if (!parsed.options.reject) { + return error; } - throw err; + throw error; } return { - stdout: handleOutput(parsed.opts, result.stdout), - stderr: handleOutput(parsed.opts, result.stderr), - code: 0, + command, + exitCode: 0, + stdout, + stderr, failed: false, - signal: null, - cmd: joinedCmd, - timedOut: false + timedOut: false, + isCanceled: false, + killed: false }; }; -module.exports.shellSync = (cmd, opts) => handleShell(module.exports.sync, cmd, opts); +module.exports.command = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa(file, args, options); +}; + +module.exports.commandSync = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa.sync(file, args, options); +}; + +module.exports.node = (scriptPath, args, options = {}) => { + if (args && !Array.isArray(args) && typeof args === 'object') { + options = args; + args = []; + } + + const stdio = normalizeStdio.node(options); + + const {nodePath = process.execPath, nodeOptions = process.execArgv} = options; + + return execa( + nodePath, + [ + ...nodeOptions, + scriptPath, + ...(Array.isArray(args) ? args : []) + ], + { + ...options, + stdin: undefined, + stdout: undefined, + stderr: undefined, + stdio, + shell: false + } + ); +}; /***/ }), @@ -19530,7 +19427,7 @@ module.exports = require("child_process"); const cp = __webpack_require__(123); const parse = __webpack_require__(125); -const enoent = __webpack_require__(138); +const enoent = __webpack_require__(136); function spawn(command, args, options) { // Parse the arguments @@ -19575,19 +19472,14 @@ module.exports._enoent = enoent; const path = __webpack_require__(16); -const niceTry = __webpack_require__(126); -const resolveCommand = __webpack_require__(127); -const escape = __webpack_require__(133); -const readShebang = __webpack_require__(134); -const semver = __webpack_require__(137); +const resolveCommand = __webpack_require__(126); +const escape = __webpack_require__(132); +const readShebang = __webpack_require__(133); const isWin = process.platform === 'win32'; const isExecutableRegExp = /\.(?:com|exe)$/i; const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i; -// `options.shell` is supported in Node ^4.8.0, ^5.7.0 and >= 6.0.0 -const supportsShellOption = niceTry(() => semver.satisfies(process.version, '^4.8.0 || ^5.7.0 || >= 6.0.0', true)) || false; - function detectShebang(parsed) { parsed.file = resolveCommand(parsed); @@ -19641,35 +19533,6 @@ function parseNonShell(parsed) { return parsed; } -function parseShell(parsed) { - // If node supports the shell option, there's no need to mimic its behavior - if (supportsShellOption) { - return parsed; - } - - // Mimic node shell option - // See https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335 - const shellCommand = [parsed.command].concat(parsed.args).join(' '); - - if (isWin) { - parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe'; - parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`]; - parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped - } else { - if (typeof parsed.options.shell === 'string') { - parsed.command = parsed.options.shell; - } else if (process.platform === 'android') { - parsed.command = '/system/bin/sh'; - } else { - parsed.command = '/bin/sh'; - } - - parsed.args = ['-c', shellCommand]; - } - - return parsed; -} - function parse(command, args, options) { // Normalize arguments, similar to nodejs if (args && !Array.isArray(args)) { @@ -19693,7 +19556,7 @@ function parse(command, args, options) { }; // Delegate further parsing to shell or non-shell - return options.shell ? parseShell(parsed) : parseNonShell(parsed); + return options.shell ? parsed : parseNonShell(parsed); } module.exports = parse; @@ -19706,36 +19569,19 @@ module.exports = parse; "use strict"; -/** - * Tries to execute a function and discards any error that occurs. - * @param {Function} fn - Function that might or might not throw an error. - * @returns {?*} Return-value of the function when no error occurred. - */ -module.exports = function(fn) { - - try { return fn() } - catch (e) {} - -} - -/***/ }), -/* 127 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - const path = __webpack_require__(16); -const which = __webpack_require__(128); -const pathKey = __webpack_require__(132)(); +const which = __webpack_require__(127); +const pathKey = __webpack_require__(131)(); function resolveCommandAttempt(parsed, withoutPathExt) { const cwd = process.cwd(); const hasCustomCwd = parsed.options.cwd != null; + // Worker threads do not have process.chdir() + const shouldSwitchCwd = hasCustomCwd && process.chdir !== undefined; // If a custom `cwd` was specified, we need to change the process cwd // because `which` will do stat calls but does not support a custom cwd - if (hasCustomCwd) { + if (shouldSwitchCwd) { try { process.chdir(parsed.options.cwd); } catch (err) { @@ -19753,7 +19599,9 @@ function resolveCommandAttempt(parsed, withoutPathExt) { } catch (e) { /* Empty */ } finally { - process.chdir(cwd); + if (shouldSwitchCwd) { + process.chdir(cwd); + } } // If we successfully resolved, ensure that an absolute path is returned @@ -19773,126 +19621,113 @@ module.exports = resolveCommand; /***/ }), -/* 128 */ +/* 127 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = which -which.sync = whichSync - -var isWindows = process.platform === 'win32' || +const isWindows = process.platform === 'win32' || process.env.OSTYPE === 'cygwin' || process.env.OSTYPE === 'msys' -var path = __webpack_require__(16) -var COLON = isWindows ? ';' : ':' -var isexe = __webpack_require__(129) - -function getNotFoundError (cmd) { - var er = new Error('not found: ' + cmd) - er.code = 'ENOENT' +const path = __webpack_require__(16) +const COLON = isWindows ? ';' : ':' +const isexe = __webpack_require__(128) - return er -} +const getNotFoundError = (cmd) => + Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) -function getPathInfo (cmd, opt) { - var colon = opt.colon || COLON - var pathEnv = opt.path || process.env.PATH || '' - var pathExt = [''] +const getPathInfo = (cmd, opt) => { + const colon = opt.colon || COLON - pathEnv = pathEnv.split(colon) + // If it has a slash, then we don't bother searching the pathenv. + // just check the file itself, and that's it. + const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [''] + : ( + [ + // windows always checks the cwd first + ...(isWindows ? [process.cwd()] : []), + ...(opt.path || process.env.PATH || + /* istanbul ignore next: very unusual */ '').split(colon), + ] + ) + const pathExtExe = isWindows + ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' + : '' + const pathExt = isWindows ? pathExtExe.split(colon) : [''] - var pathExtExe = '' if (isWindows) { - pathEnv.unshift(process.cwd()) - pathExtExe = (opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM') - pathExt = pathExtExe.split(colon) - - - // Always test the cmd itself first. isexe will check to make sure - // it's found in the pathExt set. if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') pathExt.unshift('') } - // If it has a slash, then we don't bother searching the pathenv. - // just check the file itself, and that's it. - if (cmd.match(/\//) || isWindows && cmd.match(/\\/)) - pathEnv = [''] - return { - env: pathEnv, - ext: pathExt, - extExe: pathExtExe + pathEnv, + pathExt, + pathExtExe, } } -function which (cmd, opt, cb) { +const which = (cmd, opt, cb) => { if (typeof opt === 'function') { cb = opt opt = {} } + if (!opt) + opt = {} - var info = getPathInfo(cmd, opt) - var pathEnv = info.env - var pathExt = info.ext - var pathExtExe = info.extExe - var found = [] + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) + const found = [] - ;(function F (i, l) { - if (i === l) { - if (opt.all && found.length) - return cb(null, found) - else - return cb(getNotFoundError(cmd)) - } + const step = i => new Promise((resolve, reject) => { + if (i === pathEnv.length) + return opt.all && found.length ? resolve(found) + : reject(getNotFoundError(cmd)) - var pathPart = pathEnv[i] - if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"') - pathPart = pathPart.slice(1, -1) + const ppRaw = pathEnv[i] + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw - var p = path.join(pathPart, cmd) - if (!pathPart && (/^\.[\\\/]/).test(cmd)) { - p = cmd.slice(0, 2) + p - } - ;(function E (ii, ll) { - if (ii === ll) return F(i + 1, l) - var ext = pathExt[ii] - isexe(p + ext, { pathExt: pathExtExe }, function (er, is) { - if (!er && is) { - if (opt.all) - found.push(p + ext) - else - return cb(null, p + ext) - } - return E(ii + 1, ll) - }) - })(0, pathExt.length) - })(0, pathEnv.length) + const pCmd = path.join(pathPart, cmd) + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd + + resolve(subStep(p, i, 0)) + }) + + const subStep = (p, i, ii) => new Promise((resolve, reject) => { + if (ii === pathExt.length) + return resolve(step(i + 1)) + const ext = pathExt[ii] + isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { + if (!er && is) { + if (opt.all) + found.push(p + ext) + else + return resolve(p + ext) + } + return resolve(subStep(p, i, ii + 1)) + }) + }) + + return cb ? step(0).then(res => cb(null, res), cb) : step(0) } -function whichSync (cmd, opt) { +const whichSync = (cmd, opt) => { opt = opt || {} - var info = getPathInfo(cmd, opt) - var pathEnv = info.env - var pathExt = info.ext - var pathExtExe = info.extExe - var found = [] + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) + const found = [] - for (var i = 0, l = pathEnv.length; i < l; i ++) { - var pathPart = pathEnv[i] - if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"') - pathPart = pathPart.slice(1, -1) + for (let i = 0; i < pathEnv.length; i ++) { + const ppRaw = pathEnv[i] + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw - var p = path.join(pathPart, cmd) - if (!pathPart && /^\.[\\\/]/.test(cmd)) { - p = cmd.slice(0, 2) + p - } - for (var j = 0, ll = pathExt.length; j < ll; j ++) { - var cur = p + pathExt[j] - var is + const pCmd = path.join(pathPart, cmd) + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd + + for (let j = 0; j < pathExt.length; j ++) { + const cur = p + pathExt[j] try { - is = isexe.sync(cur, { pathExt: pathExtExe }) + const is = isexe.sync(cur, { pathExt: pathExtExe }) if (is) { if (opt.all) found.push(cur) @@ -19912,17 +19747,20 @@ function whichSync (cmd, opt) { throw getNotFoundError(cmd) } +module.exports = which +which.sync = whichSync + /***/ }), -/* 129 */ +/* 128 */ /***/ (function(module, exports, __webpack_require__) { var fs = __webpack_require__(23) var core if (process.platform === 'win32' || global.TESTING_WINDOWS) { - core = __webpack_require__(130) + core = __webpack_require__(129) } else { - core = __webpack_require__(131) + core = __webpack_require__(130) } module.exports = isexe @@ -19977,7 +19815,7 @@ function sync (path, options) { /***/ }), -/* 130 */ +/* 129 */ /***/ (function(module, exports, __webpack_require__) { module.exports = isexe @@ -20025,7 +19863,7 @@ function sync (path, options) { /***/ }), -/* 131 */ +/* 130 */ /***/ (function(module, exports, __webpack_require__) { module.exports = isexe @@ -20072,27 +19910,30 @@ function checkMode (stat, options) { /***/ }), -/* 132 */ +/* 131 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = opts => { - opts = opts || {}; - const env = opts.env || process.env; - const platform = opts.platform || process.platform; +const pathKey = (options = {}) => { + const environment = options.env || process.env; + const platform = options.platform || process.platform; if (platform !== 'win32') { return 'PATH'; } - return Object.keys(env).find(x => x.toUpperCase() === 'PATH') || 'Path'; + return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path'; }; +module.exports = pathKey; +// TODO: Remove this for the next major release +module.exports.default = pathKey; + /***/ }), -/* 133 */ +/* 132 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -20144,28 +19985,19 @@ module.exports.argument = escapeArgument; /***/ }), -/* 134 */ +/* 133 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const shebangCommand = __webpack_require__(135); +const shebangCommand = __webpack_require__(134); function readShebang(command) { // Read the first 150 bytes from the file const size = 150; - let buffer; - - if (Buffer.alloc) { - // Node.js v4.5+ / v5.10+ - buffer = Buffer.alloc(size); - } else { - // Old Node.js API - buffer = new Buffer(size); - buffer.fill(0); // zero-fill - } + const buffer = Buffer.alloc(size); let fd; @@ -20183,1560 +20015,1113 @@ module.exports = readShebang; /***/ }), -/* 135 */ +/* 134 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var shebangRegex = __webpack_require__(136); +const shebangRegex = __webpack_require__(135); -module.exports = function (str) { - var match = str.match(shebangRegex); +module.exports = (string = '') => { + const match = string.match(shebangRegex); if (!match) { return null; } - var arr = match[0].replace(/#! ?/, '').split(' '); - var bin = arr[0].split('/').pop(); - var arg = arr[1]; + const [path, argument] = match[0].replace(/#! ?/, '').split(' '); + const binary = path.split('/').pop(); - return (bin === 'env' ? - arg : - bin + (arg ? ' ' + arg : '') - ); + if (binary === 'env') { + return argument; + } + + return argument ? `${binary} ${argument}` : binary; }; /***/ }), -/* 136 */ +/* 135 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = /^#!.*/; +module.exports = /^#!(.*)/; /***/ }), -/* 137 */ -/***/ (function(module, exports) { +/* 136 */ +/***/ (function(module, exports, __webpack_require__) { -exports = module.exports = SemVer; +"use strict"; -// The debug function is excluded entirely from the minified version. -/* nomin */ var debug; -/* nomin */ if (typeof process === 'object' && - /* nomin */ process.env && - /* nomin */ process.env.NODE_DEBUG && - /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) - /* nomin */ debug = function() { - /* nomin */ var args = Array.prototype.slice.call(arguments, 0); - /* nomin */ args.unshift('SEMVER'); - /* nomin */ console.log.apply(console, args); - /* nomin */ }; -/* nomin */ else - /* nomin */ debug = function() {}; -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -exports.SEMVER_SPEC_VERSION = '2.0.0'; +const isWin = process.platform === 'win32'; -var MAX_LENGTH = 256; -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; +function notFoundError(original, syscall) { + return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { + code: 'ENOENT', + errno: 'ENOENT', + syscall: `${syscall} ${original.command}`, + path: original.command, + spawnargs: original.args, + }); +} -// Max safe segment length for coercion. -var MAX_SAFE_COMPONENT_LENGTH = 16; +function hookChildProcess(cp, parsed) { + if (!isWin) { + return; + } -// The actual regexps go on exports.re -var re = exports.re = []; -var src = exports.src = []; -var R = 0; + const originalEmit = cp.emit; -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. + cp.emit = function (name, arg1) { + // If emitting "exit" event and exit code is 1, we need to check if + // the command exists and emit an "error" instead + // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 + if (name === 'exit') { + const err = verifyENOENT(arg1, parsed, 'spawn'); -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. + if (err) { + return originalEmit.call(cp, 'error', err); + } + } -var NUMERICIDENTIFIER = R++; -src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; -var NUMERICIDENTIFIERLOOSE = R++; -src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; + return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params + }; +} +function verifyENOENT(status, parsed) { + if (isWin && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawn'); + } -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. + return null; +} -var NONNUMERICIDENTIFIER = R++; -src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; +function verifyENOENTSync(status, parsed) { + if (isWin && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawnSync'); + } + return null; +} -// ## Main Version -// Three dot-separated numeric identifiers. +module.exports = { + hookChildProcess, + verifyENOENT, + verifyENOENTSync, + notFoundError, +}; -var MAINVERSION = R++; -src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')'; -var MAINVERSIONLOOSE = R++; -src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; +/***/ }), +/* 137 */ +/***/ (function(module, exports, __webpack_require__) { -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. +"use strict"; -var PRERELEASEIDENTIFIER = R++; -src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; -var PRERELEASEIDENTIFIERLOOSE = R++; -src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; +module.exports = input => { + const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); + const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); + if (input[input.length - 1] === LF) { + input = input.slice(0, input.length - 1); + } -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. + if (input[input.length - 1] === CR) { + input = input.slice(0, input.length - 1); + } -var PRERELEASE = R++; -src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + - '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; + return input; +}; -var PRERELEASELOOSE = R++; -src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + - '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. +/***/ }), +/* 138 */ +/***/ (function(module, exports, __webpack_require__) { -var BUILDIDENTIFIER = R++; -src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; +"use strict"; -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. +const path = __webpack_require__(16); +const pathKey = __webpack_require__(131); -var BUILD = R++; -src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + - '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; +const npmRunPath = options => { + options = { + cwd: process.cwd(), + path: process.env[pathKey()], + execPath: process.execPath, + ...options + }; + let previous; + let cwdPath = path.resolve(options.cwd); + const result = []; -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. + while (previous !== cwdPath) { + result.push(path.join(cwdPath, 'node_modules/.bin')); + previous = cwdPath; + cwdPath = path.resolve(cwdPath, '..'); + } -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. + // Ensure the running `node` binary is used + const execPathDir = path.resolve(options.cwd, options.execPath, '..'); + result.unshift(execPathDir); -var FULL = R++; -var FULLPLAIN = 'v?' + src[MAINVERSION] + - src[PRERELEASE] + '?' + - src[BUILD] + '?'; + return result.concat(options.path).join(path.delimiter); +}; -src[FULL] = '^' + FULLPLAIN + '$'; +module.exports = npmRunPath; +// TODO: Remove this for the next major release +module.exports.default = npmRunPath; -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + - src[PRERELEASELOOSE] + '?' + - src[BUILD] + '?'; +module.exports.env = options => { + options = { + env: process.env, + ...options + }; -var LOOSE = R++; -src[LOOSE] = '^' + LOOSEPLAIN + '$'; + const env = {...options.env}; + const path = pathKey({env}); -var GTLT = R++; -src[GTLT] = '((?:<|>)?=?)'; + options.path = env[path]; + env[path] = module.exports(options); -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -var XRANGEIDENTIFIERLOOSE = R++; -src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; -var XRANGEIDENTIFIER = R++; -src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; + return env; +}; -var XRANGEPLAIN = R++; -src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:' + src[PRERELEASE] + ')?' + - src[BUILD] + '?' + - ')?)?'; -var XRANGEPLAINLOOSE = R++; -src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:' + src[PRERELEASELOOSE] + ')?' + - src[BUILD] + '?' + - ')?)?'; +/***/ }), +/* 139 */ +/***/ (function(module, exports, __webpack_require__) { -var XRANGE = R++; -src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; -var XRANGELOOSE = R++; -src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; +"use strict"; -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -var COERCE = R++; -src[COERCE] = '(?:^|[^\\d])' + - '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:$|[^\\d])'; +const mimicFn = __webpack_require__(140); -// Tilde ranges. -// Meaning is "reasonably at or greater than" -var LONETILDE = R++; -src[LONETILDE] = '(?:~>?)'; +const calledFunctions = new WeakMap(); -var TILDETRIM = R++; -src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; -re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); -var tildeTrimReplace = '$1~'; +const oneTime = (fn, options = {}) => { + if (typeof fn !== 'function') { + throw new TypeError('Expected a function'); + } -var TILDE = R++; -src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; -var TILDELOOSE = R++; -src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; + let ret; + let isCalled = false; + let callCount = 0; + const functionName = fn.displayName || fn.name || ''; -// Caret ranges. -// Meaning is "at least and backwards compatible with" -var LONECARET = R++; -src[LONECARET] = '(?:\\^)'; + const onetime = function (...args) { + calledFunctions.set(onetime, ++callCount); -var CARETTRIM = R++; -src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; -re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); -var caretTrimReplace = '$1^'; + if (isCalled) { + if (options.throw === true) { + throw new Error(`Function \`${functionName}\` can only be called once`); + } -var CARET = R++; -src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; -var CARETLOOSE = R++; -src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; + return ret; + } -// A simple gt/lt/eq thing, or just "" to indicate "any version" -var COMPARATORLOOSE = R++; -src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; -var COMPARATOR = R++; -src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; + isCalled = true; + ret = fn.apply(this, args); + fn = null; + return ret; + }; -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -var COMPARATORTRIM = R++; -src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + - '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; + mimicFn(onetime, fn); + calledFunctions.set(onetime, callCount); -// this one has to use the /g flag -re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); -var comparatorTrimReplace = '$1$2$3'; + return onetime; +}; +module.exports = oneTime; +// TODO: Remove this for the next major release +module.exports.default = oneTime; -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -var HYPHENRANGE = R++; -src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAIN] + ')' + - '\\s*$'; +module.exports.callCount = fn => { + if (!calledFunctions.has(fn)) { + throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); + } -var HYPHENRANGELOOSE = R++; -src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s*$'; + return calledFunctions.get(fn); +}; -// Star ranges basically just allow anything at all. -var STAR = R++; -src[STAR] = '(<|>)?=?\\s*\\*'; -// Compile to actual regexp objects. -// All are flag-free, unless they were created above with a flag. -for (var i = 0; i < R; i++) { - debug(i, src[i]); - if (!re[i]) - re[i] = new RegExp(src[i]); -} +/***/ }), +/* 140 */ +/***/ (function(module, exports, __webpack_require__) { -exports.parse = parse; -function parse(version, loose) { - if (version instanceof SemVer) - return version; +"use strict"; - if (typeof version !== 'string') - return null; - if (version.length > MAX_LENGTH) - return null; +const mimicFn = (to, from) => { + for (const prop of Reflect.ownKeys(from)) { + Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + } - var r = loose ? re[LOOSE] : re[FULL]; - if (!r.test(version)) - return null; + return to; +}; - try { - return new SemVer(version, loose); - } catch (er) { - return null; - } -} +module.exports = mimicFn; +// TODO: Remove this for the next major release +module.exports.default = mimicFn; -exports.valid = valid; -function valid(version, loose) { - var v = parse(version, loose); - return v ? v.version : null; -} +/***/ }), +/* 141 */ +/***/ (function(module, exports, __webpack_require__) { -exports.clean = clean; -function clean(version, loose) { - var s = parse(version.trim().replace(/^[=v]+/, ''), loose); - return s ? s.version : null; -} +"use strict"; -exports.SemVer = SemVer; +const {signalsByName} = __webpack_require__(142); -function SemVer(version, loose) { - if (version instanceof SemVer) { - if (version.loose === loose) - return version; - else - version = version.version; - } else if (typeof version !== 'string') { - throw new TypeError('Invalid Version: ' + version); - } +const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { + if (timedOut) { + return `timed out after ${timeout} milliseconds`; + } - if (version.length > MAX_LENGTH) - throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') + if (isCanceled) { + return 'was canceled'; + } - if (!(this instanceof SemVer)) - return new SemVer(version, loose); + if (errorCode !== undefined) { + return `failed with ${errorCode}`; + } - debug('SemVer', version, loose); - this.loose = loose; - var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); + if (signal !== undefined) { + return `was killed with ${signal} (${signalDescription})`; + } - if (!m) - throw new TypeError('Invalid Version: ' + version); + if (exitCode !== undefined) { + return `failed with exit code ${exitCode}`; + } - this.raw = version; + return 'failed'; +}; + +const makeError = ({ + stdout, + stderr, + all, + error, + signal, + exitCode, + command, + timedOut, + isCanceled, + killed, + parsed: {options: {timeout}} +}) => { + // `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`. + // We normalize them to `undefined` + exitCode = exitCode === null ? undefined : exitCode; + signal = signal === null ? undefined : signal; + const signalDescription = signal === undefined ? undefined : signalsByName[signal].description; + + const errorCode = error && error.code; + + const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); + const message = `Command ${prefix}: ${command}`; + + if (error instanceof Error) { + error.originalMessage = error.message; + error.message = `${message}\n${error.message}`; + } else { + error = new Error(message); + } - // these are actually numbers - this.major = +m[1]; - this.minor = +m[2]; - this.patch = +m[3]; + error.command = command; + error.exitCode = exitCode; + error.signal = signal; + error.signalDescription = signalDescription; + error.stdout = stdout; + error.stderr = stderr; - if (this.major > MAX_SAFE_INTEGER || this.major < 0) - throw new TypeError('Invalid major version') + if (all !== undefined) { + error.all = all; + } - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) - throw new TypeError('Invalid minor version') + if ('bufferedData' in error) { + delete error.bufferedData; + } - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) - throw new TypeError('Invalid patch version') + error.failed = true; + error.timedOut = Boolean(timedOut); + error.isCanceled = isCanceled; + error.killed = killed && !timedOut; - // numberify any prerelease numeric ids - if (!m[4]) - this.prerelease = []; - else - this.prerelease = m[4].split('.').map(function(id) { - if (/^[0-9]+$/.test(id)) { - var num = +id; - if (num >= 0 && num < MAX_SAFE_INTEGER) - return num; - } - return id; - }); + return error; +}; - this.build = m[5] ? m[5].split('.') : []; - this.format(); -} +module.exports = makeError; -SemVer.prototype.format = function() { - this.version = this.major + '.' + this.minor + '.' + this.patch; - if (this.prerelease.length) - this.version += '-' + this.prerelease.join('.'); - return this.version; -}; -SemVer.prototype.toString = function() { - return this.version; -}; +/***/ }), +/* 142 */ +/***/ (function(module, exports, __webpack_require__) { -SemVer.prototype.compare = function(other) { - debug('SemVer.compare', this.version, this.loose, other); - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(11); - return this.compareMain(other) || this.comparePre(other); +var _signals=__webpack_require__(143); +var _realtime=__webpack_require__(145); + + + +const getSignalsByName=function(){ +const signals=(0,_signals.getSignals)(); +return signals.reduce(getSignalByName,{}); }; -SemVer.prototype.compareMain = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); +const getSignalByName=function( +signalByNameMemo, +{name,number,description,supported,action,forced,standard}) +{ +return{ +...signalByNameMemo, +[name]:{name,number,description,supported,action,forced,standard}}; - return compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch); }; -SemVer.prototype.comparePre = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); +const signalsByName=getSignalsByName();exports.signalsByName=signalsByName; - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) - return -1; - else if (!this.prerelease.length && other.prerelease.length) - return 1; - else if (!this.prerelease.length && !other.prerelease.length) - return 0; - var i = 0; - do { - var a = this.prerelease[i]; - var b = other.prerelease[i]; - debug('prerelease compare', i, a, b); - if (a === undefined && b === undefined) - return 0; - else if (b === undefined) - return 1; - else if (a === undefined) - return -1; - else if (a === b) - continue; - else - return compareIdentifiers(a, b); - } while (++i); -}; -// preminor will bump the version up to the next minor release, and immediately -// down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function(release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0; - this.patch = 0; - this.minor = 0; - this.major++; - this.inc('pre', identifier); - break; - case 'preminor': - this.prerelease.length = 0; - this.patch = 0; - this.minor++; - this.inc('pre', identifier); - break; - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0; - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) - this.major++; - this.minor = 0; - this.patch = 0; - this.prerelease = []; - break; - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) - this.minor++; - this.patch = 0; - this.prerelease = []; - break; - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) - this.patch++; - this.prerelease = []; - break; - // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) - this.prerelease = [0]; - else { - var i = this.prerelease.length; - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++; - i = -2; - } - } - if (i === -1) // didn't increment anything - this.prerelease.push(0); - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) - this.prerelease = [identifier, 0]; - } else - this.prerelease = [identifier, 0]; - } - break; +const getSignalsByNumber=function(){ +const signals=(0,_signals.getSignals)(); +const length=_realtime.SIGRTMAX+1; +const signalsA=Array.from({length},(value,number)=> +getSignalByNumber(number,signals)); - default: - throw new Error('invalid increment argument: ' + release); - } - this.format(); - this.raw = this.version; - return this; +return Object.assign({},...signalsA); }; -exports.inc = inc; -function inc(version, release, loose, identifier) { - if (typeof(loose) === 'string') { - identifier = loose; - loose = undefined; - } +const getSignalByNumber=function(number,signals){ +const signal=findSignalByNumber(number,signals); - try { - return new SemVer(version, loose).inc(release, identifier).version; - } catch (er) { - return null; - } +if(signal===undefined){ +return{}; } -exports.diff = diff; -function diff(version1, version2) { - if (eq(version1, version2)) { - return null; - } else { - var v1 = parse(version1); - var v2 = parse(version2); - if (v1.prerelease.length || v2.prerelease.length) { - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return 'pre'+key; - } - } - } - return 'prerelease'; - } - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return key; - } - } - } - } -} +const{name,description,supported,action,forced,standard}=signal; +return{ +[number]:{ +name, +number, +description, +supported, +action, +forced, +standard}}; -exports.compareIdentifiers = compareIdentifiers; -var numeric = /^[0-9]+$/; -function compareIdentifiers(a, b) { - var anum = numeric.test(a); - var bnum = numeric.test(b); +}; - if (anum && bnum) { - a = +a; - b = +b; - } - return (anum && !bnum) ? -1 : - (bnum && !anum) ? 1 : - a < b ? -1 : - a > b ? 1 : - 0; -} -exports.rcompareIdentifiers = rcompareIdentifiers; -function rcompareIdentifiers(a, b) { - return compareIdentifiers(b, a); -} +const findSignalByNumber=function(number,signals){ +const signal=signals.find(({name})=>_os.constants.signals[name]===number); -exports.major = major; -function major(a, loose) { - return new SemVer(a, loose).major; +if(signal!==undefined){ +return signal; } -exports.minor = minor; -function minor(a, loose) { - return new SemVer(a, loose).minor; -} +return signals.find(signalA=>signalA.number===number); +}; -exports.patch = patch; -function patch(a, loose) { - return new SemVer(a, loose).patch; -} +const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumber; +//# sourceMappingURL=main.js.map -exports.compare = compare; -function compare(a, b, loose) { - return new SemVer(a, loose).compare(new SemVer(b, loose)); -} +/***/ }), +/* 143 */ +/***/ (function(module, exports, __webpack_require__) { -exports.compareLoose = compareLoose; -function compareLoose(a, b) { - return compare(a, b, true); -} +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(11); -exports.rcompare = rcompare; -function rcompare(a, b, loose) { - return compare(b, a, loose); -} +var _core=__webpack_require__(144); +var _realtime=__webpack_require__(145); -exports.sort = sort; -function sort(list, loose) { - return list.sort(function(a, b) { - return exports.compare(a, b, loose); - }); -} -exports.rsort = rsort; -function rsort(list, loose) { - return list.sort(function(a, b) { - return exports.rcompare(a, b, loose); - }); -} -exports.gt = gt; -function gt(a, b, loose) { - return compare(a, b, loose) > 0; -} +const getSignals=function(){ +const realtimeSignals=(0,_realtime.getRealtimeSignals)(); +const signals=[..._core.SIGNALS,...realtimeSignals].map(normalizeSignal); +return signals; +};exports.getSignals=getSignals; -exports.lt = lt; -function lt(a, b, loose) { - return compare(a, b, loose) < 0; -} -exports.eq = eq; -function eq(a, b, loose) { - return compare(a, b, loose) === 0; -} -exports.neq = neq; -function neq(a, b, loose) { - return compare(a, b, loose) !== 0; -} -exports.gte = gte; -function gte(a, b, loose) { - return compare(a, b, loose) >= 0; -} -exports.lte = lte; -function lte(a, b, loose) { - return compare(a, b, loose) <= 0; -} -exports.cmp = cmp; -function cmp(a, op, b, loose) { - var ret; - switch (op) { - case '===': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a === b; - break; - case '!==': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a !== b; - break; - case '': case '=': case '==': ret = eq(a, b, loose); break; - case '!=': ret = neq(a, b, loose); break; - case '>': ret = gt(a, b, loose); break; - case '>=': ret = gte(a, b, loose); break; - case '<': ret = lt(a, b, loose); break; - case '<=': ret = lte(a, b, loose); break; - default: throw new TypeError('Invalid operator: ' + op); - } - return ret; -} -exports.Comparator = Comparator; -function Comparator(comp, loose) { - if (comp instanceof Comparator) { - if (comp.loose === loose) - return comp; - else - comp = comp.value; - } +const normalizeSignal=function({ +name, +number:defaultNumber, +description, +action, +forced=false, +standard}) +{ +const{ +signals:{[name]:constantSignal}}= +_os.constants; +const supported=constantSignal!==undefined; +const number=supported?constantSignal:defaultNumber; +return{name,number,description,supported,action,forced,standard}; +}; +//# sourceMappingURL=signals.js.map - if (!(this instanceof Comparator)) - return new Comparator(comp, loose); +/***/ }), +/* 144 */ +/***/ (function(module, exports, __webpack_require__) { - debug('comparator', comp, loose); - this.loose = loose; - this.parse(comp); +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.SIGNALS=void 0; + +const SIGNALS=[ +{ +name:"SIGHUP", +number:1, +action:"terminate", +description:"Terminal closed", +standard:"posix"}, + +{ +name:"SIGINT", +number:2, +action:"terminate", +description:"User interruption with CTRL-C", +standard:"ansi"}, + +{ +name:"SIGQUIT", +number:3, +action:"core", +description:"User interruption with CTRL-\\", +standard:"posix"}, + +{ +name:"SIGILL", +number:4, +action:"core", +description:"Invalid machine instruction", +standard:"ansi"}, + +{ +name:"SIGTRAP", +number:5, +action:"core", +description:"Debugger breakpoint", +standard:"posix"}, + +{ +name:"SIGABRT", +number:6, +action:"core", +description:"Aborted", +standard:"ansi"}, + +{ +name:"SIGIOT", +number:6, +action:"core", +description:"Aborted", +standard:"bsd"}, + +{ +name:"SIGBUS", +number:7, +action:"core", +description: +"Bus error due to misaligned, non-existing address or paging error", +standard:"bsd"}, + +{ +name:"SIGEMT", +number:7, +action:"terminate", +description:"Command should be emulated but is not implemented", +standard:"other"}, + +{ +name:"SIGFPE", +number:8, +action:"core", +description:"Floating point arithmetic error", +standard:"ansi"}, + +{ +name:"SIGKILL", +number:9, +action:"terminate", +description:"Forced termination", +standard:"posix", +forced:true}, + +{ +name:"SIGUSR1", +number:10, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGSEGV", +number:11, +action:"core", +description:"Segmentation fault", +standard:"ansi"}, + +{ +name:"SIGUSR2", +number:12, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGPIPE", +number:13, +action:"terminate", +description:"Broken pipe or socket", +standard:"posix"}, + +{ +name:"SIGALRM", +number:14, +action:"terminate", +description:"Timeout or timer", +standard:"posix"}, + +{ +name:"SIGTERM", +number:15, +action:"terminate", +description:"Termination", +standard:"ansi"}, + +{ +name:"SIGSTKFLT", +number:16, +action:"terminate", +description:"Stack is empty or overflowed", +standard:"other"}, + +{ +name:"SIGCHLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"posix"}, + +{ +name:"SIGCLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"other"}, + +{ +name:"SIGCONT", +number:18, +action:"unpause", +description:"Unpaused", +standard:"posix", +forced:true}, + +{ +name:"SIGSTOP", +number:19, +action:"pause", +description:"Paused", +standard:"posix", +forced:true}, + +{ +name:"SIGTSTP", +number:20, +action:"pause", +description:"Paused using CTRL-Z or \"suspend\"", +standard:"posix"}, + +{ +name:"SIGTTIN", +number:21, +action:"pause", +description:"Background process cannot read terminal input", +standard:"posix"}, + +{ +name:"SIGBREAK", +number:21, +action:"terminate", +description:"User interruption with CTRL-BREAK", +standard:"other"}, + +{ +name:"SIGTTOU", +number:22, +action:"pause", +description:"Background process cannot write to terminal output", +standard:"posix"}, + +{ +name:"SIGURG", +number:23, +action:"ignore", +description:"Socket received out-of-band data", +standard:"bsd"}, + +{ +name:"SIGXCPU", +number:24, +action:"core", +description:"Process timed out", +standard:"bsd"}, + +{ +name:"SIGXFSZ", +number:25, +action:"core", +description:"File too big", +standard:"bsd"}, + +{ +name:"SIGVTALRM", +number:26, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGPROF", +number:27, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGWINCH", +number:28, +action:"ignore", +description:"Terminal window size changed", +standard:"bsd"}, + +{ +name:"SIGIO", +number:29, +action:"terminate", +description:"I/O is available", +standard:"other"}, + +{ +name:"SIGPOLL", +number:29, +action:"terminate", +description:"Watched event", +standard:"other"}, + +{ +name:"SIGINFO", +number:29, +action:"ignore", +description:"Request for process information", +standard:"other"}, + +{ +name:"SIGPWR", +number:30, +action:"terminate", +description:"Device running out of power", +standard:"systemv"}, + +{ +name:"SIGSYS", +number:31, +action:"core", +description:"Invalid system call", +standard:"other"}, + +{ +name:"SIGUNUSED", +number:31, +action:"terminate", +description:"Invalid system call", +standard:"other"}];exports.SIGNALS=SIGNALS; +//# sourceMappingURL=core.js.map - if (this.semver === ANY) - this.value = ''; - else - this.value = this.operator + this.semver.version; +/***/ }), +/* 145 */ +/***/ (function(module, exports, __webpack_require__) { - debug('comp', this); -} +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.SIGRTMAX=exports.getRealtimeSignals=void 0; +const getRealtimeSignals=function(){ +const length=SIGRTMAX-SIGRTMIN+1; +return Array.from({length},getRealtimeSignal); +};exports.getRealtimeSignals=getRealtimeSignals; -var ANY = {}; -Comparator.prototype.parse = function(comp) { - var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var m = comp.match(r); +const getRealtimeSignal=function(value,index){ +return{ +name:`SIGRT${index+1}`, +number:SIGRTMIN+index, +action:"terminate", +description:"Application-specific signal (realtime)", +standard:"posix"}; - if (!m) - throw new TypeError('Invalid comparator: ' + comp); +}; - this.operator = m[1]; - if (this.operator === '=') - this.operator = ''; +const SIGRTMIN=34; +const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; +//# sourceMappingURL=realtime.js.map - // if it literally is just '>' or '' then allow anything. - if (!m[2]) - this.semver = ANY; - else - this.semver = new SemVer(m[2], this.loose); -}; +/***/ }), +/* 146 */ +/***/ (function(module, exports, __webpack_require__) { -Comparator.prototype.toString = function() { - return this.value; -}; +"use strict"; -Comparator.prototype.test = function(version) { - debug('Comparator.test', version, this.loose); +const aliases = ['stdin', 'stdout', 'stderr']; - if (this.semver === ANY) - return true; +const hasAlias = opts => aliases.some(alias => opts[alias] !== undefined); - if (typeof version === 'string') - version = new SemVer(version, this.loose); +const normalizeStdio = opts => { + if (!opts) { + return; + } - return cmp(version, this.operator, this.semver, this.loose); -}; + const {stdio} = opts; -Comparator.prototype.intersects = function(comp, loose) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required'); - } + if (stdio === undefined) { + return aliases.map(alias => opts[alias]); + } - var rangeTmp; + if (hasAlias(opts)) { + throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`); + } - if (this.operator === '') { - rangeTmp = new Range(comp.value, loose); - return satisfies(this.value, rangeTmp, loose); - } else if (comp.operator === '') { - rangeTmp = new Range(this.value, loose); - return satisfies(comp.semver, rangeTmp, loose); - } + if (typeof stdio === 'string') { + return stdio; + } - var sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>'); - var sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<'); - var sameSemVer = this.semver.version === comp.semver.version; - var differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<='); - var oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, loose) && - ((this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<')); - var oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, loose) && - ((this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>')); + if (!Array.isArray(stdio)) { + throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); + } - return sameDirectionIncreasing || sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; + const length = Math.max(stdio.length, aliases.length); + return Array.from({length}, (value, index) => stdio[index]); }; +module.exports = normalizeStdio; -exports.Range = Range; -function Range(range, loose) { - if (range instanceof Range) { - if (range.loose === loose) { - return range; - } else { - return new Range(range.raw, loose); - } - } +// `ipc` is pushed unless it is already present +module.exports.node = opts => { + const stdio = normalizeStdio(opts); - if (range instanceof Comparator) { - return new Range(range.value, loose); - } + if (stdio === 'ipc') { + return 'ipc'; + } - if (!(this instanceof Range)) - return new Range(range, loose); + if (stdio === undefined || typeof stdio === 'string') { + return [stdio, stdio, stdio, 'ipc']; + } - this.loose = loose; + if (stdio.includes('ipc')) { + return stdio; + } - // First, split based on boolean or || - this.raw = range; - this.set = range.split(/\s*\|\|\s*/).map(function(range) { - return this.parseRange(range.trim()); - }, this).filter(function(c) { - // throw out any that are not relevant for whatever reason - return c.length; - }); + return [...stdio, 'ipc']; +}; - if (!this.set.length) { - throw new TypeError('Invalid SemVer Range: ' + range); - } - this.format(); -} +/***/ }), +/* 147 */ +/***/ (function(module, exports, __webpack_require__) { -Range.prototype.format = function() { - this.range = this.set.map(function(comps) { - return comps.join(' ').trim(); - }).join('||').trim(); - return this.range; +"use strict"; + +const os = __webpack_require__(11); +const onExit = __webpack_require__(110); +const pFinally = __webpack_require__(148); + +const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; + +// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior +const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => { + const killResult = kill(signal); + setKillTimeout(kill, signal, options, killResult); + return killResult; }; -Range.prototype.toString = function() { - return this.range; +const setKillTimeout = (kill, signal, options, killResult) => { + if (!shouldForceKill(signal, options, killResult)) { + return; + } + + const timeout = getForceKillAfterTimeout(options); + setTimeout(() => { + kill('SIGKILL'); + }, timeout).unref(); }; -Range.prototype.parseRange = function(range) { - var loose = this.loose; - range = range.trim(); - debug('range', range, loose); - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; - range = range.replace(hr, hyphenReplace); - debug('hyphen replace', range); - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); - debug('comparator trim', range, re[COMPARATORTRIM]); +const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => { + return isSigterm(signal) && forceKillAfterTimeout !== false && killResult; +}; - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace); +const isSigterm = signal => { + return signal === os.constants.signals.SIGTERM || + (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); +}; - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace); +const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => { + if (forceKillAfterTimeout === true) { + return DEFAULT_FORCE_KILL_TIMEOUT; + } - // normalize spaces - range = range.split(/\s+/).join(' '); + if (!Number.isInteger(forceKillAfterTimeout) || forceKillAfterTimeout < 0) { + throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`); + } - // At this point, the range is completely trimmed and - // ready to be split into comparators. + return forceKillAfterTimeout; +}; - var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var set = range.split(' ').map(function(comp) { - return parseComparator(comp, loose); - }).join(' ').split(/\s+/); - if (this.loose) { - // in loose mode, throw out any that are not valid comparators - set = set.filter(function(comp) { - return !!comp.match(compRe); - }); - } - set = set.map(function(comp) { - return new Comparator(comp, loose); - }); +// `childProcess.cancel()` +const spawnedCancel = (spawned, context) => { + const killResult = spawned.kill(); - return set; + if (killResult) { + context.isCanceled = true; + } }; -Range.prototype.intersects = function(range, loose) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required'); - } +const timeoutKill = (spawned, signal, reject) => { + spawned.kill(signal); + reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); +}; - return this.set.some(function(thisComparators) { - return thisComparators.every(function(thisComparator) { - return range.set.some(function(rangeComparators) { - return rangeComparators.every(function(rangeComparator) { - return thisComparator.intersects(rangeComparator, loose); - }); - }); - }); - }); +// `timeout` option handling +const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { + if (timeout === 0 || timeout === undefined) { + return spawnedPromise; + } + + if (!Number.isInteger(timeout) || timeout < 0) { + throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); + } + + let timeoutId; + const timeoutPromise = new Promise((resolve, reject) => { + timeoutId = setTimeout(() => { + timeoutKill(spawned, killSignal, reject); + }, timeout); + }); + + const safeSpawnedPromise = pFinally(spawnedPromise, () => { + clearTimeout(timeoutId); + }); + + return Promise.race([timeoutPromise, safeSpawnedPromise]); }; -// Mostly just for testing and legacy API reasons -exports.toComparators = toComparators; -function toComparators(range, loose) { - return new Range(range, loose).set.map(function(comp) { - return comp.map(function(c) { - return c.value; - }).join(' ').trim().split(' '); - }); -} +// `cleanup` option handling +const setExitHandler = (spawned, {cleanup, detached}, timedPromise) => { + if (!cleanup || detached) { + return timedPromise; + } -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -function parseComparator(comp, loose) { - debug('comp', comp); - comp = replaceCarets(comp, loose); - debug('caret', comp); - comp = replaceTildes(comp, loose); - debug('tildes', comp); - comp = replaceXRanges(comp, loose); - debug('xrange', comp); - comp = replaceStars(comp, loose); - debug('stars', comp); - return comp; -} + const removeExitHandler = onExit(() => { + spawned.kill(); + }); -function isX(id) { - return !id || id.toLowerCase() === 'x' || id === '*'; -} + // TODO: Use native "finally" syntax when targeting Node.js 10 + return pFinally(timedPromise, removeExitHandler); +}; -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 -function replaceTildes(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceTilde(comp, loose); - }).join(' '); -} +module.exports = { + spawnedKill, + spawnedCancel, + setupTimeout, + setExitHandler +}; -function replaceTilde(comp, loose) { - var r = loose ? re[TILDELOOSE] : re[TILDE]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('tilde', comp, _, M, m, p, pr); - var ret; - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) - // ~1.2 == >=1.2.0 <1.3.0 - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else if (pr) { - debug('replaceTilde pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; +/***/ }), +/* 148 */ +/***/ (function(module, exports, __webpack_require__) { - debug('tilde return', ret); - return ret; - }); -} +"use strict"; -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2.0 --> >=1.2.0 <2.0.0 -function replaceCarets(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceCaret(comp, loose); - }).join(' '); -} -function replaceCaret(comp, loose) { - debug('caret', comp, loose); - var r = loose ? re[CARETLOOSE] : re[CARET]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('caret', comp, _, M, m, p, pr); - var ret; +module.exports = async ( + promise, + onFinally = (() => {}) +) => { + let value; + try { + value = await promise; + } catch (error) { + await onFinally(); + throw error; + } - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) { - if (M === '0') - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else - ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; - } else if (pr) { - debug('replaceCaret pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + (+M + 1) + '.0.0'; - } else { - debug('no pr'); - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + (+M + 1) + '.0.0'; - } + await onFinally(); + return value; +}; - debug('caret return', ret); - return ret; - }); -} -function replaceXRanges(comp, loose) { - debug('replaceXRanges', comp, loose); - return comp.split(/\s+/).map(function(comp) { - return replaceXRange(comp, loose); - }).join(' '); -} +/***/ }), +/* 149 */ +/***/ (function(module, exports, __webpack_require__) { -function replaceXRange(comp, loose) { - comp = comp.trim(); - var r = loose ? re[XRANGELOOSE] : re[XRANGE]; - return comp.replace(r, function(ret, gtlt, M, m, p, pr) { - debug('xRange', comp, ret, gtlt, M, m, p, pr); - var xM = isX(M); - var xm = xM || isX(m); - var xp = xm || isX(p); - var anyX = xp; +"use strict"; - if (gtlt === '=' && anyX) - gtlt = ''; +const isStream = __webpack_require__(150); +const getStream = __webpack_require__(151); +const mergeStream = __webpack_require__(155); - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0'; - } else { - // nothing is forbidden - ret = '*'; - } - } else if (gtlt && anyX) { - // replace X with 0 - if (xm) - m = 0; - if (xp) - p = 0; +// `input` option +const handleInput = (spawned, input) => { + // Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 + // TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 + if (input === undefined || spawned.stdin === undefined) { + return; + } - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - gtlt = '>='; - if (xm) { - M = +M + 1; - m = 0; - p = 0; - } else if (xp) { - m = +m + 1; - p = 0; - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<'; - if (xm) - M = +M + 1; - else - m = +m + 1; - } + if (isStream(input)) { + input.pipe(spawned.stdin); + } else { + spawned.stdin.end(input); + } +}; - ret = gtlt + M + '.' + m + '.' + p; - } else if (xm) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - } else if (xp) { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - } +// `all` interleaves `stdout` and `stderr` +const makeAllStream = (spawned, {all}) => { + if (!all || (!spawned.stdout && !spawned.stderr)) { + return; + } - debug('xRange return', ret); + const mixed = mergeStream(); - return ret; - }); -} + if (spawned.stdout) { + mixed.add(spawned.stdout); + } -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -function replaceStars(comp, loose) { - debug('replaceStars', comp, loose); - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], ''); -} + if (spawned.stderr) { + mixed.add(spawned.stderr); + } -// This function is passed to string.replace(re[HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) { + return mixed; +}; - if (isX(fM)) - from = ''; - else if (isX(fm)) - from = '>=' + fM + '.0.0'; - else if (isX(fp)) - from = '>=' + fM + '.' + fm + '.0'; - else - from = '>=' + from; +// On failure, `result.stdout|stderr|all` should contain the currently buffered stream +const getBufferedData = async (stream, streamPromise) => { + if (!stream) { + return; + } - if (isX(tM)) - to = ''; - else if (isX(tm)) - to = '<' + (+tM + 1) + '.0.0'; - else if (isX(tp)) - to = '<' + tM + '.' + (+tm + 1) + '.0'; - else if (tpr) - to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; - else - to = '<=' + to; + stream.destroy(); - return (from + ' ' + to).trim(); -} + try { + return await streamPromise; + } catch (error) { + return error.bufferedData; + } +}; +const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { + if (!stream || !buffer) { + return; + } -// if ANY of the sets match ALL of its comparators, then pass -Range.prototype.test = function(version) { - if (!version) - return false; + if (encoding) { + return getStream(stream, {encoding, maxBuffer}); + } - if (typeof version === 'string') - version = new SemVer(version, this.loose); + return getStream.buffer(stream, {maxBuffer}); +}; - for (var i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version)) - return true; - } - return false; +// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all) +const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { + const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); + const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); + const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); + + try { + return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); + } catch (error) { + return Promise.all([ + {error, signal: error.signal, timedOut: error.timedOut}, + getBufferedData(stdout, stdoutPromise), + getBufferedData(stderr, stderrPromise), + getBufferedData(all, allPromise) + ]); + } }; -function testSet(set, version) { - for (var i = 0; i < set.length; i++) { - if (!set[i].test(version)) - return false; - } +const validateInputSync = ({input}) => { + if (isStream(input)) { + throw new TypeError('The `input` option cannot be a stream in sync mode'); + } +}; - if (version.prerelease.length) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (var i = 0; i < set.length; i++) { - debug(set[i].semver); - if (set[i].semver === ANY) - continue; +module.exports = { + handleInput, + makeAllStream, + getSpawnedResult, + validateInputSync +}; - if (set[i].semver.prerelease.length > 0) { - var allowed = set[i].semver; - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) - return true; - } - } - // Version has a -pre, but it's not one of the ones we like. - return false; - } - return true; -} +/***/ }), +/* 150 */ +/***/ (function(module, exports, __webpack_require__) { -exports.satisfies = satisfies; -function satisfies(version, range, loose) { - try { - range = new Range(range, loose); - } catch (er) { - return false; - } - return range.test(version); -} +"use strict"; -exports.maxSatisfying = maxSatisfying; -function maxSatisfying(versions, range, loose) { - var max = null; - var maxSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) - max = v; - maxSV = new SemVer(max, loose); - } - } - }) - return max; -} -exports.minSatisfying = minSatisfying; -function minSatisfying(versions, range, loose) { - var min = null; - var minSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!min || minSV.compare(v) === 1) { // compare(min, v, true) - min = v; - minSV = new SemVer(min, loose); - } - } - }) - return min; -} +const isStream = stream => + stream !== null && + typeof stream === 'object' && + typeof stream.pipe === 'function'; -exports.validRange = validRange; -function validRange(range, loose) { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, loose).range || '*'; - } catch (er) { - return null; - } -} +isStream.writable = stream => + isStream(stream) && + stream.writable !== false && + typeof stream._write === 'function' && + typeof stream._writableState === 'object'; -// Determine if version is less than all the versions possible in the range -exports.ltr = ltr; -function ltr(version, range, loose) { - return outside(version, range, '<', loose); -} +isStream.readable = stream => + isStream(stream) && + stream.readable !== false && + typeof stream._read === 'function' && + typeof stream._readableState === 'object'; -// Determine if version is greater than all the versions possible in the range. -exports.gtr = gtr; -function gtr(version, range, loose) { - return outside(version, range, '>', loose); -} +isStream.duplex = stream => + isStream.writable(stream) && + isStream.readable(stream); -exports.outside = outside; -function outside(version, range, hilo, loose) { - version = new SemVer(version, loose); - range = new Range(range, loose); +isStream.transform = stream => + isStream.duplex(stream) && + typeof stream._transform === 'function' && + typeof stream._transformState === 'object'; - var gtfn, ltefn, ltfn, comp, ecomp; - switch (hilo) { - case '>': - gtfn = gt; - ltefn = lte; - ltfn = lt; - comp = '>'; - ecomp = '>='; - break; - case '<': - gtfn = lt; - ltefn = gte; - ltfn = gt; - comp = '<'; - ecomp = '<='; - break; - default: - throw new TypeError('Must provide a hilo val of "<" or ">"'); - } +module.exports = isStream; - // If it satisifes the range it is not outside - if (satisfies(version, range, loose)) { - return false; - } - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. +/***/ }), +/* 151 */ +/***/ (function(module, exports, __webpack_require__) { - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i]; +"use strict"; - var high = null; - var low = null; - - comparators.forEach(function(comparator) { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator; - low = low || comparator; - if (gtfn(comparator.semver, high.semver, loose)) { - high = comparator; - } else if (ltfn(comparator.semver, low.semver, loose)) { - low = comparator; - } - }); - - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false; - } - - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false; - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false; - } - } - return true; -} - -exports.prerelease = prerelease; -function prerelease(version, loose) { - var parsed = parse(version, loose); - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; -} - -exports.intersects = intersects; -function intersects(r1, r2, loose) { - r1 = new Range(r1, loose) - r2 = new Range(r2, loose) - return r1.intersects(r2) -} - -exports.coerce = coerce; -function coerce(version) { - if (version instanceof SemVer) - return version; - - if (typeof version !== 'string') - return null; - - var match = version.match(re[COERCE]); - - if (match == null) - return null; - - return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0')); -} - - -/***/ }), -/* 138 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const isWin = process.platform === 'win32'; - -function notFoundError(original, syscall) { - return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { - code: 'ENOENT', - errno: 'ENOENT', - syscall: `${syscall} ${original.command}`, - path: original.command, - spawnargs: original.args, - }); -} - -function hookChildProcess(cp, parsed) { - if (!isWin) { - return; - } - - const originalEmit = cp.emit; - - cp.emit = function (name, arg1) { - // If emitting "exit" event and exit code is 1, we need to check if - // the command exists and emit an "error" instead - // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 - if (name === 'exit') { - const err = verifyENOENT(arg1, parsed, 'spawn'); - - if (err) { - return originalEmit.call(cp, 'error', err); - } - } - - return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params - }; -} - -function verifyENOENT(status, parsed) { - if (isWin && status === 1 && !parsed.file) { - return notFoundError(parsed.original, 'spawn'); - } - - return null; -} - -function verifyENOENTSync(status, parsed) { - if (isWin && status === 1 && !parsed.file) { - return notFoundError(parsed.original, 'spawnSync'); - } - - return null; -} - -module.exports = { - hookChildProcess, - verifyENOENT, - verifyENOENTSync, - notFoundError, -}; - - -/***/ }), -/* 139 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = function (x) { - var lf = typeof x === 'string' ? '\n' : '\n'.charCodeAt(); - var cr = typeof x === 'string' ? '\r' : '\r'.charCodeAt(); - - if (x[x.length - 1] === lf) { - x = x.slice(0, x.length - 1); - } - - if (x[x.length - 1] === cr) { - x = x.slice(0, x.length - 1); - } - - return x; -}; - - -/***/ }), -/* 140 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const path = __webpack_require__(16); -const pathKey = __webpack_require__(141); - -module.exports = opts => { - opts = Object.assign({ - cwd: process.cwd(), - path: process.env[pathKey()] - }, opts); - - let prev; - let pth = path.resolve(opts.cwd); - const ret = []; - - while (prev !== pth) { - ret.push(path.join(pth, 'node_modules/.bin')); - prev = pth; - pth = path.resolve(pth, '..'); - } - - // ensure the running `node` binary is used - ret.push(path.dirname(process.execPath)); - - return ret.concat(opts.path).join(path.delimiter); -}; - -module.exports.env = opts => { - opts = Object.assign({ - env: process.env - }, opts); - - const env = Object.assign({}, opts.env); - const path = pathKey({env}); - - opts.path = env[path]; - env[path] = module.exports(opts); - - return env; -}; - - -/***/ }), -/* 141 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = opts => { - opts = opts || {}; - - const env = opts.env || process.env; - const platform = opts.platform || process.platform; - - if (platform !== 'win32') { - return 'PATH'; - } - - return Object.keys(env).find(x => x.toUpperCase() === 'PATH') || 'Path'; -}; - - -/***/ }), -/* 142 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isStream = module.exports = function (stream) { - return stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'; -}; - -isStream.writable = function (stream) { - return isStream(stream) && stream.writable !== false && typeof stream._write === 'function' && typeof stream._writableState === 'object'; -}; - -isStream.readable = function (stream) { - return isStream(stream) && stream.readable !== false && typeof stream._read === 'function' && typeof stream._readableState === 'object'; -}; - -isStream.duplex = function (stream) { - return isStream.writable(stream) && isStream.readable(stream); -}; - -isStream.transform = function (stream) { - return isStream.duplex(stream) && typeof stream._transform === 'function' && typeof stream._transformState === 'object'; -}; - - -/***/ }), -/* 143 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const pump = __webpack_require__(144); -const bufferStream = __webpack_require__(146); +const pump = __webpack_require__(152); +const bufferStream = __webpack_require__(154); class MaxBufferError extends Error { constructor() { @@ -21745,21 +21130,25 @@ class MaxBufferError extends Error { } } -function getStream(inputStream, options) { +async function getStream(inputStream, options) { if (!inputStream) { return Promise.reject(new Error('Expected a stream')); } - options = Object.assign({maxBuffer: Infinity}, options); + options = { + maxBuffer: Infinity, + ...options + }; const {maxBuffer} = options; let stream; - return new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const rejectPromise = error => { if (error) { // A null check error.bufferedData = stream.getBufferedValue(); } + reject(error); }; @@ -21777,21 +21166,25 @@ function getStream(inputStream, options) { rejectPromise(new MaxBufferError()); } }); - }).then(() => stream.getBufferedValue()); + }); + + return stream.getBufferedValue(); } module.exports = getStream; -module.exports.buffer = (stream, options) => getStream(stream, Object.assign({}, options, {encoding: 'buffer'})); -module.exports.array = (stream, options) => getStream(stream, Object.assign({}, options, {array: true})); +// TODO: Remove this for the next major release +module.exports.default = getStream; +module.exports.buffer = (stream, options) => getStream(stream, {...options, encoding: 'buffer'}); +module.exports.array = (stream, options) => getStream(stream, {...options, array: true}); module.exports.MaxBufferError = MaxBufferError; /***/ }), -/* 144 */ +/* 152 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(52) -var eos = __webpack_require__(145) +var eos = __webpack_require__(153) var fs = __webpack_require__(23) // we only need fs to get the ReadStream and WriteStream prototypes var noop = function () {} @@ -21875,7 +21268,7 @@ module.exports = pump /***/ }), -/* 145 */ +/* 153 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(52); @@ -21968,186 +21361,223 @@ module.exports = eos; /***/ }), -/* 146 */ +/* 154 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {PassThrough} = __webpack_require__(28); +const {PassThrough: PassThroughStream} = __webpack_require__(28); module.exports = options => { - options = Object.assign({}, options); + options = {...options}; const {array} = options; let {encoding} = options; - const buffer = encoding === 'buffer'; + const isBuffer = encoding === 'buffer'; let objectMode = false; if (array) { - objectMode = !(encoding || buffer); + objectMode = !(encoding || isBuffer); } else { encoding = encoding || 'utf8'; } - if (buffer) { + if (isBuffer) { encoding = null; } - let len = 0; - const ret = []; - const stream = new PassThrough({objectMode}); + const stream = new PassThroughStream({objectMode}); if (encoding) { stream.setEncoding(encoding); } + let length = 0; + const chunks = []; + stream.on('data', chunk => { - ret.push(chunk); + chunks.push(chunk); if (objectMode) { - len = ret.length; + length = chunks.length; } else { - len += chunk.length; + length += chunk.length; } }); stream.getBufferedValue = () => { if (array) { - return ret; + return chunks; } - return buffer ? Buffer.concat(ret, len) : ret.join(''); + return isBuffer ? Buffer.concat(chunks, length) : chunks.join(''); }; - stream.getBufferedLength = () => len; + stream.getBufferedLength = () => length; return stream; }; /***/ }), -/* 147 */ +/* 155 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = (promise, onFinally) => { - onFinally = onFinally || (() => {}); - return promise.then( - val => new Promise(resolve => { - resolve(onFinally()); - }).then(() => val), - err => new Promise(resolve => { - resolve(onFinally()); - }).then(() => { - throw err; - }) - ); -}; +const { PassThrough } = __webpack_require__(28); + +module.exports = function (/*streams...*/) { + var sources = [] + var output = new PassThrough({objectMode: true}) + + output.setMaxListeners(0) + + output.add = add + output.isEmpty = isEmpty + + output.on('unpipe', remove) + + Array.prototype.slice.call(arguments).forEach(add) + + return output + + function add (source) { + if (Array.isArray(source)) { + source.forEach(add) + return this + } + + sources.push(source); + source.once('end', remove.bind(null, source)) + source.once('error', output.emit.bind(output, 'error')) + source.pipe(output, {end: false}) + return this + } + + function isEmpty () { + return sources.length == 0; + } + + function remove (source) { + sources = sources.filter(function (it) { return it !== source }) + if (!sources.length && output.readable) { output.end() } + } +} /***/ }), -/* 148 */ +/* 156 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -// Older verions of Node.js might not have `util.getSystemErrorName()`. -// In that case, fall back to a deprecated internal. -const util = __webpack_require__(29); - -let uv; +const mergePromiseProperty = (spawned, promise, property) => { + // Starting the main `promise` is deferred to avoid consuming streams + const value = typeof promise === 'function' ? + (...args) => promise()[property](...args) : + promise[property].bind(promise); + + Object.defineProperty(spawned, property, { + value, + writable: true, + enumerable: false, + configurable: true + }); +}; -if (typeof util.getSystemErrorName === 'function') { - module.exports = util.getSystemErrorName; -} else { - try { - uv = process.binding('uv'); +// The return value is a mixin of `childProcess` and `Promise` +const mergePromise = (spawned, promise) => { + mergePromiseProperty(spawned, promise, 'then'); + mergePromiseProperty(spawned, promise, 'catch'); - if (typeof uv.errname !== 'function') { - throw new TypeError('uv.errname is not a function'); - } - } catch (err) { - console.error('execa/lib/errname: unable to establish process.binding(\'uv\')', err); - uv = null; + // TODO: Remove the `if`-guard when targeting Node.js 10 + if (Promise.prototype.finally) { + mergePromiseProperty(spawned, promise, 'finally'); } - module.exports = code => errname(uv, code); -} + return spawned; +}; -// Used for testing the fallback behavior -module.exports.__test__ = errname; +// Use promises instead of `child_process` events +const getSpawnedPromise = spawned => { + return new Promise((resolve, reject) => { + spawned.on('exit', (exitCode, signal) => { + resolve({exitCode, signal}); + }); -function errname(uv, code) { - if (uv) { - return uv.errname(code); - } + spawned.on('error', error => { + reject(error); + }); - if (!(code < 0)) { - throw new Error('err >= 0'); - } + if (spawned.stdin) { + spawned.stdin.on('error', error => { + reject(error); + }); + } + }); +}; - return `Unknown system error ${code}`; -} +module.exports = { + mergePromise, + getSpawnedPromise +}; /***/ }), -/* 149 */ +/* 157 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const alias = ['stdin', 'stdout', 'stderr']; - -const hasAlias = opts => alias.some(x => Boolean(opts[x])); +const SPACES_REGEXP = / +/g; -module.exports = opts => { - if (!opts) { - return null; +const joinCommand = (file, args = []) => { + if (!Array.isArray(args)) { + return file; } - if (opts.stdio && hasAlias(opts)) { - throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${alias.map(x => `\`${x}\``).join(', ')}`); - } + return [file, ...args].join(' '); +}; - if (typeof opts.stdio === 'string') { - return opts.stdio; +// Allow spaces to be escaped by a backslash if not meant as a delimiter +const handleEscaping = (tokens, token, index) => { + if (index === 0) { + return [token]; } - const stdio = opts.stdio || []; + const previousToken = tokens[tokens.length - 1]; - if (!Array.isArray(stdio)) { - throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); + if (previousToken.endsWith('\\')) { + return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`]; } - const result = []; - const len = Math.max(stdio.length, alias.length); - - for (let i = 0; i < len; i++) { - let value = null; - - if (stdio[i] !== undefined) { - value = stdio[i]; - } else if (opts[alias[i]] !== undefined) { - value = opts[alias[i]]; - } + return [...tokens, token]; +}; - result[i] = value; - } +// Handle `execa.command()` +const parseCommand = command => { + return command + .trim() + .split(SPACES_REGEXP) + .reduce(handleEscaping, []); +}; - return result; +module.exports = { + joinCommand, + parseCommand }; /***/ }), -/* 150 */ +/* 158 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(151); +const chalk = __webpack_require__(159); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -22169,16 +21599,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 151 */ +/* 159 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(152); -const stdoutColor = __webpack_require__(153).stdout; +const ansiStyles = __webpack_require__(160); +const stdoutColor = __webpack_require__(161).stdout; -const template = __webpack_require__(154); +const template = __webpack_require__(162); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -22404,7 +21834,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 152 */ +/* 160 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22577,7 +22007,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 153 */ +/* 161 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22719,7 +22149,7 @@ module.exports = { /***/ }), -/* 154 */ +/* 162 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22854,7 +22284,7 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 155 */ +/* 163 */ /***/ (function(module, exports, __webpack_require__) { // Copyright IBM Corp. 2014,2018. All Rights Reserved. @@ -22862,12 +22292,12 @@ module.exports = (chalk, tmp) => { // This file is licensed under the Apache License 2.0. // License text available at https://opensource.org/licenses/Apache-2.0 -module.exports = __webpack_require__(156); -module.exports.cli = __webpack_require__(160); +module.exports = __webpack_require__(164); +module.exports.cli = __webpack_require__(168); /***/ }), -/* 156 */ +/* 164 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22882,9 +22312,9 @@ var stream = __webpack_require__(28); var util = __webpack_require__(29); var fs = __webpack_require__(23); -var through = __webpack_require__(157); -var duplexer = __webpack_require__(158); -var StringDecoder = __webpack_require__(159).StringDecoder; +var through = __webpack_require__(165); +var duplexer = __webpack_require__(166); +var StringDecoder = __webpack_require__(167).StringDecoder; module.exports = Logger; @@ -23073,7 +22503,7 @@ function lineMerger(host) { /***/ }), -/* 157 */ +/* 165 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(28) @@ -23187,7 +22617,7 @@ function through (write, end, opts) { /***/ }), -/* 158 */ +/* 166 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(28) @@ -23280,13 +22710,13 @@ function duplex(writer, reader) { /***/ }), -/* 159 */ +/* 167 */ /***/ (function(module, exports) { module.exports = require("string_decoder"); /***/ }), -/* 160 */ +/* 168 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -23297,11 +22727,11 @@ module.exports = require("string_decoder"); -var minimist = __webpack_require__(161); +var minimist = __webpack_require__(169); var path = __webpack_require__(16); -var Logger = __webpack_require__(156); -var pkg = __webpack_require__(162); +var Logger = __webpack_require__(164); +var pkg = __webpack_require__(170); module.exports = cli; @@ -23355,7 +22785,7 @@ function usage($0, p) { /***/ }), -/* 161 */ +/* 169 */ /***/ (function(module, exports) { module.exports = function (args, opts) { @@ -23597,13 +23027,13 @@ function isNumber (x) { /***/ }), -/* 162 */ +/* 170 */ /***/ (function(module) { module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); /***/ }), -/* 163 */ +/* 171 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -23616,7 +23046,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); /* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); /* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(55); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); @@ -23645,1157 +23075,8428 @@ __webpack_require__.r(__webpack_exports__); -const glob = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(glob__WEBPACK_IMPORTED_MODULE_0___default.a); -async function workspacePackagePaths(rootPath) { - const rootPkgJson = await Object(_package_json__WEBPACK_IMPORTED_MODULE_5__["readPackageJson"])(rootPath); +const glob = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(glob__WEBPACK_IMPORTED_MODULE_0___default.a); +async function workspacePackagePaths(rootPath) { + const rootPkgJson = await Object(_package_json__WEBPACK_IMPORTED_MODULE_5__["readPackageJson"])(rootPath); + + if (!rootPkgJson.workspaces) { + return []; + } + + const workspacesPathsPatterns = rootPkgJson.workspaces.packages; + let workspaceProjectsPaths = []; + + for (const pattern of workspacesPathsPatterns) { + workspaceProjectsPaths = workspaceProjectsPaths.concat((await packagesFromGlobPattern({ + pattern, + rootPath + }))); + } // Filter out exclude glob patterns + + + for (const pattern of workspacesPathsPatterns) { + if (pattern.startsWith('!')) { + const pathToRemove = path__WEBPACK_IMPORTED_MODULE_1___default.a.join(rootPath, pattern.slice(1), 'package.json'); + workspaceProjectsPaths = workspaceProjectsPaths.filter(p => p !== pathToRemove); + } + } + + return workspaceProjectsPaths; +} +async function copyWorkspacePackages(rootPath) { + const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(rootPath, {}); + const projects = await Object(_projects__WEBPACK_IMPORTED_MODULE_6__["getProjects"])(rootPath, projectPaths); + + for (const project of projects.values()) { + const dest = path__WEBPACK_IMPORTED_MODULE_1___default.a.resolve(rootPath, 'node_modules', project.name); + + if ((await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["isSymlink"])(dest)) === false) { + continue; + } // Remove the symlink + + + await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["unlink"])(dest); // Copy in the package + + await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["copyDirectory"])(project.path, dest); + } +} + +function packagesFromGlobPattern({ + pattern, + rootPath +}) { + const globOptions = { + cwd: rootPath, + // Should throw in case of unusual errors when reading the file system + strict: true, + // Always returns absolute paths for matched files + absolute: true, + // Do not match ** against multiple filenames + // (This is only specified because we currently don't have a need for it.) + noglobstar: true + }; + return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); +} + +/***/ }), +/* 172 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_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. + */ + + +/** + * Returns all the paths where plugins are located + */ +function getProjectPaths(rootPath, options = {}) { + const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']); + const ossOnly = Boolean(options.oss); + const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared + // plugin functional used in the selenium functional tests. + // As we are now using the webpack dll for the client vendors dependencies + // when we run the plugin functional tests against the distributable + // dependencies used by such plugins like @eui, react and react-dom can't + // be loaded from the dll as the context is different from the one declared + // into the webpack dll reference plugin. + // In anyway, have a plugin declaring their own dependencies is the + // correct and the expect behavior. + + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); + + if (!ossOnly) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); + } + + if (!skipKibanaPlugins) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); + } + + return projectPaths; +} + +/***/ }), +/* 173 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(174); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); +/* + * 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. + */ + + + + + + +const CleanCommand = { + description: 'Remove the node_modules and target directories from all projects.', + name: 'clean', + + async run(projects) { + const toDelete = []; + + for (const project of projects.values()) { + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.nodeModulesLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) + }); + } + + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.targetLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) + }); + } + + const { + extraPatterns + } = project.getCleanConfig(); + + if (extraPatterns) { + toDelete.push({ + cwd: project.path, + pattern: extraPatterns + }); + } + } + + if (toDelete.length === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.green('\n\nNothing to delete')); + } else { + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red('\n\nDeleting:\n')); + /** + * In order to avoid patterns like `/build` in packages from accidentally + * impacting files outside the package we use `process.chdir()` to change + * the cwd to the package and execute `del()` without the `force` option + * so it will check that each file being deleted is within the package. + * + * `del()` does support a `cwd` option, but it's only for resolving the + * patterns and does not impact the cwd check. + */ + + const originalCwd = process.cwd(); + + try { + for (const _ref of toDelete) { + const { + pattern, + cwd + } = _ref; + process.chdir(cwd); + const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); + ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); + await promise; + } + } finally { + process.chdir(originalCwd); + } + } + } + +}; + +/***/ }), +/* 174 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const path = __webpack_require__(16); +const globby = __webpack_require__(175); +const isGlob = __webpack_require__(187); +const slash = __webpack_require__(248); +const gracefulFs = __webpack_require__(250); +const isPathCwd = __webpack_require__(254); +const isPathInside = __webpack_require__(255); +const rimraf = __webpack_require__(256); +const pMap = __webpack_require__(257); + +const rimrafP = promisify(rimraf); + +const rimrafOptions = { + glob: false, + unlink: gracefulFs.unlink, + unlinkSync: gracefulFs.unlinkSync, + chmod: gracefulFs.chmod, + chmodSync: gracefulFs.chmodSync, + stat: gracefulFs.stat, + statSync: gracefulFs.statSync, + lstat: gracefulFs.lstat, + lstatSync: gracefulFs.lstatSync, + rmdir: gracefulFs.rmdir, + rmdirSync: gracefulFs.rmdirSync, + readdir: gracefulFs.readdir, + readdirSync: gracefulFs.readdirSync +}; + +function safeCheck(file, cwd) { + if (isPathCwd(file)) { + throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.'); + } + + if (!isPathInside(file, cwd)) { + throw new Error('Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'); + } +} + +function normalizePatterns(patterns) { + patterns = Array.isArray(patterns) ? patterns : [patterns]; + + patterns = patterns.map(pattern => { + if (process.platform === 'win32' && isGlob(pattern) === false) { + return slash(pattern); + } + + return pattern; + }); + + return patterns; +} + +module.exports = async (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { + options = { + expandDirectories: false, + onlyFiles: false, + followSymbolicLinks: false, + cwd, + ...options + }; + + patterns = normalizePatterns(patterns); + + const files = (await globby(patterns, options)) + .sort((a, b) => b.localeCompare(a)); + + const mapper = async file => { + file = path.resolve(cwd, file); + + if (!force) { + safeCheck(file, cwd); + } + + if (!dryRun) { + await rimrafP(file, rimrafOptions); + } + + return file; + }; + + const removedFiles = await pMap(files, mapper, options); + + removedFiles.sort((a, b) => a.localeCompare(b)); + + return removedFiles; +}; + +module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { + options = { + expandDirectories: false, + onlyFiles: false, + followSymbolicLinks: false, + cwd, + ...options + }; + + patterns = normalizePatterns(patterns); + + const files = globby.sync(patterns, options) + .sort((a, b) => b.localeCompare(a)); + + const removedFiles = files.map(file => { + file = path.resolve(cwd, file); + + if (!force) { + safeCheck(file, cwd); + } + + if (!dryRun) { + rimraf.sync(file, rimrafOptions); + } + + return file; + }); + + removedFiles.sort((a, b) => a.localeCompare(b)); + + return removedFiles; +}; + + +/***/ }), +/* 175 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const fs = __webpack_require__(23); +const arrayUnion = __webpack_require__(176); +const merge2 = __webpack_require__(177); +const glob = __webpack_require__(37); +const fastGlob = __webpack_require__(178); +const dirGlob = __webpack_require__(244); +const gitignore = __webpack_require__(246); +const {FilterStream, UniqueStream} = __webpack_require__(249); + +const DEFAULT_FILTER = () => false; + +const isNegative = pattern => pattern[0] === '!'; + +const assertPatternsInput = patterns => { + if (!patterns.every(pattern => typeof pattern === 'string')) { + throw new TypeError('Patterns must be a string or an array of strings'); + } +}; + +const checkCwdOption = (options = {}) => { + if (!options.cwd) { + return; + } + + let stat; + try { + stat = fs.statSync(options.cwd); + } catch (_) { + return; + } + + if (!stat.isDirectory()) { + throw new Error('The `cwd` option must be a path to a directory'); + } +}; + +const getPathString = p => p.stats instanceof fs.Stats ? p.path : p; + +const generateGlobTasks = (patterns, taskOptions) => { + patterns = arrayUnion([].concat(patterns)); + assertPatternsInput(patterns); + checkCwdOption(taskOptions); + + const globTasks = []; + + taskOptions = { + ignore: [], + expandDirectories: true, + ...taskOptions + }; + + for (const [index, pattern] of patterns.entries()) { + if (isNegative(pattern)) { + continue; + } + + const ignore = patterns + .slice(index) + .filter(isNegative) + .map(pattern => pattern.slice(1)); + + const options = { + ...taskOptions, + ignore: taskOptions.ignore.concat(ignore) + }; + + globTasks.push({pattern, options}); + } + + return globTasks; +}; + +const globDirs = (task, fn) => { + let options = {}; + if (task.options.cwd) { + options.cwd = task.options.cwd; + } + + if (Array.isArray(task.options.expandDirectories)) { + options = { + ...options, + files: task.options.expandDirectories + }; + } else if (typeof task.options.expandDirectories === 'object') { + options = { + ...options, + ...task.options.expandDirectories + }; + } + + return fn(task.pattern, options); +}; + +const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; + +const getFilterSync = options => { + return options && options.gitignore ? + gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; +}; + +const globToTask = task => glob => { + const {options} = task; + if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { + options.ignore = dirGlob.sync(options.ignore); + } + + return { + pattern: glob, + options + }; +}; + +module.exports = async (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const getFilter = async () => { + return options && options.gitignore ? + gitignore({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; + }; + + const getTasks = async () => { + const tasks = await Promise.all(globTasks.map(async task => { + const globs = await getPattern(task, dirGlob); + return Promise.all(globs.map(globToTask(task))); + })); + + return arrayUnion(...tasks); + }; + + const [filter, tasks] = await Promise.all([getFilter(), getTasks()]); + const paths = await Promise.all(tasks.map(task => fastGlob(task.pattern, task.options))); + + return arrayUnion(...paths).filter(path_ => !filter(getPathString(path_))); +}; + +module.exports.sync = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + + const filter = getFilterSync(options); + + return tasks.reduce( + (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), + [] + ).filter(path_ => !filter(path_)); +}; + +module.exports.stream = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + + const filter = getFilterSync(options); + const filterStream = new FilterStream(p => !filter(p)); + const uniqueStream = new UniqueStream(); + + return merge2(tasks.map(task => fastGlob.stream(task.pattern, task.options))) + .pipe(filterStream) + .pipe(uniqueStream); +}; + +module.exports.generateGlobTasks = generateGlobTasks; + +module.exports.hasMagic = (patterns, options) => [] + .concat(patterns) + .some(pattern => glob.hasMagic(pattern, options)); + +module.exports.gitignore = gitignore; + + +/***/ }), +/* 176 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = (...arguments_) => { + return [...new Set([].concat(...arguments_))]; +}; + + +/***/ }), +/* 177 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * merge2 + * https://github.com/teambition/merge2 + * + * Copyright (c) 2014-2016 Teambition + * Licensed under the MIT license. + */ +const Stream = __webpack_require__(28) +const PassThrough = Stream.PassThrough +const slice = Array.prototype.slice + +module.exports = merge2 + +function merge2 () { + const streamsQueue = [] + let merging = false + const args = slice.call(arguments) + let options = args[args.length - 1] + + if (options && !Array.isArray(options) && options.pipe == null) args.pop() + else options = {} + + const doEnd = options.end !== false + if (options.objectMode == null) options.objectMode = true + if (options.highWaterMark == null) options.highWaterMark = 64 * 1024 + const mergedStream = PassThrough(options) + + function addStream () { + for (let i = 0, len = arguments.length; i < len; i++) { + streamsQueue.push(pauseStreams(arguments[i], options)) + } + mergeStream() + return this + } + + function mergeStream () { + if (merging) return + merging = true + + let streams = streamsQueue.shift() + if (!streams) { + process.nextTick(endStream) + return + } + if (!Array.isArray(streams)) streams = [streams] + + let pipesCount = streams.length + 1 + + function next () { + if (--pipesCount > 0) return + merging = false + mergeStream() + } + + function pipe (stream) { + function onend () { + stream.removeListener('merge2UnpipeEnd', onend) + stream.removeListener('end', onend) + next() + } + // skip ended stream + if (stream._readableState.endEmitted) return next() + + stream.on('merge2UnpipeEnd', onend) + stream.on('end', onend) + stream.pipe(mergedStream, { end: false }) + // compatible for old stream + stream.resume() + } + + for (let i = 0; i < streams.length; i++) pipe(streams[i]) + + next() + } + + function endStream () { + merging = false + // emit 'queueDrain' when all streams merged. + mergedStream.emit('queueDrain') + return doEnd && mergedStream.end() + } + + mergedStream.setMaxListeners(0) + mergedStream.add = addStream + mergedStream.on('unpipe', function (stream) { + stream.emit('merge2UnpipeEnd') + }) + + if (args.length) addStream.apply(null, args) + return mergedStream +} + +// check and pause streams for pipe. +function pauseStreams (streams, options) { + if (!Array.isArray(streams)) { + // Backwards-compat with old-style streams + if (!streams._readableState && streams.pipe) streams = streams.pipe(PassThrough(options)) + if (!streams._readableState || !streams.pause || !streams.pipe) { + throw new Error('Only readable stream can be merged.') + } + streams.pause() + } else { + for (let i = 0, len = streams.length; i < len; i++) streams[i] = pauseStreams(streams[i], options) + } + return streams +} + + +/***/ }), +/* 178 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const taskManager = __webpack_require__(179); +const async_1 = __webpack_require__(207); +const stream_1 = __webpack_require__(240); +const sync_1 = __webpack_require__(241); +const settings_1 = __webpack_require__(243); +const utils = __webpack_require__(180); +function FastGlob(source, options) { + try { + assertPatternsInput(source); + } + catch (error) { + return Promise.reject(error); + } + const works = getWorks(source, async_1.default, options); + return Promise.all(works).then(utils.array.flatten); +} +(function (FastGlob) { + function sync(source, options) { + assertPatternsInput(source); + const works = getWorks(source, sync_1.default, options); + return utils.array.flatten(works); + } + FastGlob.sync = sync; + function stream(source, options) { + assertPatternsInput(source); + const works = getWorks(source, stream_1.default, options); + /** + * The stream returned by the provider cannot work with an asynchronous iterator. + * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. + * This affects performance (+25%). I don't see best solution right now. + */ + return utils.stream.merge(works); + } + FastGlob.stream = stream; + function generateTasks(source, options) { + assertPatternsInput(source); + const patterns = [].concat(source); + const settings = new settings_1.default(options); + return taskManager.generate(patterns, settings); + } + FastGlob.generateTasks = generateTasks; +})(FastGlob || (FastGlob = {})); +function getWorks(source, _Provider, options) { + const patterns = [].concat(source); + const settings = new settings_1.default(options); + const tasks = taskManager.generate(patterns, settings); + const provider = new _Provider(settings); + return tasks.map(provider.read, provider); +} +function assertPatternsInput(source) { + if ([].concat(source).every(isString)) { + return; + } + throw new TypeError('Patterns must be a string or an array of strings'); +} +function isString(source) { + /* tslint:disable-next-line strict-type-predicates */ + return typeof source === 'string'; +} +module.exports = FastGlob; + + +/***/ }), +/* 179 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +function generate(patterns, settings) { + const positivePatterns = getPositivePatterns(patterns); + const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); + /** + * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check + * filepath directly (without read directory). + */ + const staticPatterns = !settings.caseSensitiveMatch ? [] : positivePatterns.filter(utils.pattern.isStaticPattern); + const dynamicPatterns = !settings.caseSensitiveMatch ? positivePatterns : positivePatterns.filter(utils.pattern.isDynamicPattern); + const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); + const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); + return staticTasks.concat(dynamicTasks); +} +exports.generate = generate; +function convertPatternsToTasks(positive, negative, dynamic) { + const positivePatternsGroup = groupPatternsByBaseDirectory(positive); + // When we have a global group – there is no reason to divide the patterns into independent tasks. + // In this case, the global task covers the rest. + if ('.' in positivePatternsGroup) { + const task = convertPatternGroupToTask('.', positive, negative, dynamic); + return [task]; + } + return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); +} +exports.convertPatternsToTasks = convertPatternsToTasks; +function getPositivePatterns(patterns) { + return utils.pattern.getPositivePatterns(patterns); +} +exports.getPositivePatterns = getPositivePatterns; +function getNegativePatternsAsPositive(patterns, ignore) { + const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); + const positive = negative.map(utils.pattern.convertToPositivePattern); + return positive; +} +exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; +function groupPatternsByBaseDirectory(patterns) { + return patterns.reduce((collection, pattern) => { + const base = utils.pattern.getBaseDirectory(pattern); + if (base in collection) { + collection[base].push(pattern); + } + else { + collection[base] = [pattern]; + } + return collection; + }, {}); +} +exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; +function convertPatternGroupsToTasks(positive, negative, dynamic) { + return Object.keys(positive).map((base) => { + return convertPatternGroupToTask(base, positive[base], negative, dynamic); + }); +} +exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; +function convertPatternGroupToTask(base, positive, negative, dynamic) { + return { + dynamic, + positive, + negative, + base, + patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern)) + }; +} +exports.convertPatternGroupToTask = convertPatternGroupToTask; + + +/***/ }), +/* 180 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const array = __webpack_require__(181); +exports.array = array; +const errno = __webpack_require__(182); +exports.errno = errno; +const fs = __webpack_require__(183); +exports.fs = fs; +const path = __webpack_require__(184); +exports.path = path; +const pattern = __webpack_require__(185); +exports.pattern = pattern; +const stream = __webpack_require__(206); +exports.stream = stream; + + +/***/ }), +/* 181 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function flatten(items) { + return items.reduce((collection, item) => [].concat(collection, item), []); +} +exports.flatten = flatten; + + +/***/ }), +/* 182 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isEnoentCodeError(error) { + return error.code === 'ENOENT'; +} +exports.isEnoentCodeError = isEnoentCodeError; + + +/***/ }), +/* 183 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; + + +/***/ }), +/* 184 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +/** + * Designed to work only with simple paths: `dir\\file`. + */ +function unixify(filepath) { + return filepath.replace(/\\/g, '/'); +} +exports.unixify = unixify; +function makeAbsolute(cwd, filepath) { + return path.resolve(cwd, filepath); +} +exports.makeAbsolute = makeAbsolute; + + +/***/ }), +/* 185 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const globParent = __webpack_require__(186); +const isGlob = __webpack_require__(187); +const micromatch = __webpack_require__(189); +const GLOBSTAR = '**'; +function isStaticPattern(pattern) { + return !isDynamicPattern(pattern); +} +exports.isStaticPattern = isStaticPattern; +function isDynamicPattern(pattern) { + return isGlob(pattern, { strict: false }); +} +exports.isDynamicPattern = isDynamicPattern; +function convertToPositivePattern(pattern) { + return isNegativePattern(pattern) ? pattern.slice(1) : pattern; +} +exports.convertToPositivePattern = convertToPositivePattern; +function convertToNegativePattern(pattern) { + return '!' + pattern; +} +exports.convertToNegativePattern = convertToNegativePattern; +function isNegativePattern(pattern) { + return pattern.startsWith('!') && pattern[1] !== '('; +} +exports.isNegativePattern = isNegativePattern; +function isPositivePattern(pattern) { + return !isNegativePattern(pattern); +} +exports.isPositivePattern = isPositivePattern; +function getNegativePatterns(patterns) { + return patterns.filter(isNegativePattern); +} +exports.getNegativePatterns = getNegativePatterns; +function getPositivePatterns(patterns) { + return patterns.filter(isPositivePattern); +} +exports.getPositivePatterns = getPositivePatterns; +function getBaseDirectory(pattern) { + return globParent(pattern); +} +exports.getBaseDirectory = getBaseDirectory; +function hasGlobStar(pattern) { + return pattern.indexOf(GLOBSTAR) !== -1; +} +exports.hasGlobStar = hasGlobStar; +function endsWithSlashGlobStar(pattern) { + return pattern.endsWith('/' + GLOBSTAR); +} +exports.endsWithSlashGlobStar = endsWithSlashGlobStar; +function isAffectDepthOfReadingPattern(pattern) { + const basename = path.basename(pattern); + return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); +} +exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; +function getNaiveDepth(pattern) { + const base = getBaseDirectory(pattern); + const patternDepth = pattern.split('/').length; + const patternBaseDepth = base.split('/').length; + /** + * This is a hack for pattern that has no base directory. + * + * This is related to the `*\something\*` pattern. + */ + if (base === '.') { + return patternDepth - patternBaseDepth; + } + return patternDepth - patternBaseDepth - 1; +} +exports.getNaiveDepth = getNaiveDepth; +function getMaxNaivePatternsDepth(patterns) { + return patterns.reduce((max, pattern) => { + const depth = getNaiveDepth(pattern); + return depth > max ? depth : max; + }, 0); +} +exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; +function makeRe(pattern, options) { + return micromatch.makeRe(pattern, options); +} +exports.makeRe = makeRe; +function convertPatternsToRe(patterns, options) { + return patterns.map((pattern) => makeRe(pattern, options)); +} +exports.convertPatternsToRe = convertPatternsToRe; +function matchAny(entry, patternsRe) { + const filepath = entry.replace(/^\.[\\\/]/, ''); + return patternsRe.some((patternRe) => patternRe.test(filepath)); +} +exports.matchAny = matchAny; + + +/***/ }), +/* 186 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var isGlob = __webpack_require__(187); +var pathPosixDirname = __webpack_require__(16).posix.dirname; +var isWin32 = __webpack_require__(11).platform() === 'win32'; + +var slash = '/'; +var backslash = /\\/g; +var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; +var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; +var escaped = /\\([\*\?\|\[\]\(\)\{\}])/g; + +module.exports = function globParent(str) { + // flip windows path separators + if (isWin32 && str.indexOf(slash) < 0) { + str = str.replace(backslash, slash); + } + + // special case for strings ending in enclosure containing path separator + if (enclosure.test(str)) { + str += slash; + } + + // preserves full path in case of trailing path separator + str += 'a'; + + // remove path parts that are globby + do { + str = pathPosixDirname(str); + } while (isGlob(str) || globby.test(str)); + + // remove escape chars and return result + return str.replace(escaped, '$1'); +}; + + +/***/ }), +/* 187 */ +/***/ (function(module, exports, __webpack_require__) { + +/*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */ + +var isExtglob = __webpack_require__(188); +var chars = { '{': '}', '(': ')', '[': ']'}; +var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; +var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; + +module.exports = function isGlob(str, options) { + if (typeof str !== 'string' || str === '') { + return false; + } + + if (isExtglob(str)) { + return true; + } + + var regex = strictRegex; + var match; + + // optionally relax regex + if (options && options.strict === false) { + regex = relaxedRegex; + } + + while ((match = regex.exec(str))) { + if (match[2]) return true; + var idx = match.index + match[0].length; + + // if an open bracket/brace/paren is escaped, + // set the index to the next closing character + var open = match[1]; + var close = open ? chars[open] : null; + if (open && close) { + var n = str.indexOf(close, idx); + if (n !== -1) { + idx = n + 1; + } + } + + str = str.slice(idx); + } + return false; +}; + + +/***/ }), +/* 188 */ +/***/ (function(module, exports) { + +/*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + */ + +module.exports = function isExtglob(str) { + if (typeof str !== 'string' || str === '') { + return false; + } + + var match; + while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { + if (match[2]) return true; + str = str.slice(match.index + match[0].length); + } + + return false; +}; + + +/***/ }), +/* 189 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const util = __webpack_require__(29); +const braces = __webpack_require__(190); +const picomatch = __webpack_require__(200); +const utils = __webpack_require__(203); +const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); + +/** + * Returns an array of strings that match one or more glob patterns. + * + * ```js + * const mm = require('micromatch'); + * // mm(list, patterns[, options]); + * + * console.log(mm(['a.js', 'a.txt'], ['*.js'])); + * //=> [ 'a.js' ] + * ``` + * @param {String|Array} list List of strings to match. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} options See available [options](#options) + * @return {Array} Returns an array of matches + * @summary false + * @api public + */ + +const micromatch = (list, patterns, options) => { + patterns = [].concat(patterns); + list = [].concat(list); + + let omit = new Set(); + let keep = new Set(); + let items = new Set(); + let negatives = 0; + + let onResult = state => { + items.add(state.output); + if (options && options.onResult) { + options.onResult(state); + } + }; + + for (let i = 0; i < patterns.length; i++) { + let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true); + let negated = isMatch.state.negated || isMatch.state.negatedExtglob; + if (negated) negatives++; + + for (let item of list) { + let matched = isMatch(item, true); + + let match = negated ? !matched.isMatch : matched.isMatch; + if (!match) continue; + + if (negated) { + omit.add(matched.output); + } else { + omit.delete(matched.output); + keep.add(matched.output); + } + } + } + + let result = negatives === patterns.length ? [...items] : [...keep]; + let matches = result.filter(item => !omit.has(item)); + + if (options && matches.length === 0) { + if (options.failglob === true) { + throw new Error(`No matches found for "${patterns.join(', ')}"`); + } + + if (options.nonull === true || options.nullglob === true) { + return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns; + } + } + + return matches; +}; + +/** + * Backwards compatibility + */ + +micromatch.match = micromatch; + +/** + * Returns a matcher function from the given glob `pattern` and `options`. + * The returned function takes a string to match as its only argument and returns + * true if the string is a match. + * + * ```js + * const mm = require('micromatch'); + * // mm.matcher(pattern[, options]); + * + * const isMatch = mm.matcher('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @param {String} `pattern` Glob pattern + * @param {Object} `options` + * @return {Function} Returns a matcher function. + * @api public + */ + +micromatch.matcher = (pattern, options) => picomatch(pattern, options); + +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const mm = require('micromatch'); + * // mm.isMatch(string, patterns[, options]); + * + * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(mm.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + +/** + * Backwards compatibility + */ + +micromatch.any = micromatch.isMatch; + +/** + * Returns a list of strings that _**do not match any**_ of the given `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.not(list, patterns[, options]); + * + * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); + * //=> ['b.b', 'c.c'] + * ``` + * @param {Array} `list` Array of strings to match. + * @param {String|Array} `patterns` One or more glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Array} Returns an array of strings that **do not match** the given patterns. + * @api public + */ + +micromatch.not = (list, patterns, options = {}) => { + patterns = [].concat(patterns).map(String); + let result = new Set(); + let items = []; + + let onResult = state => { + if (options.onResult) options.onResult(state); + items.push(state.output); + }; + + let matches = micromatch(list, patterns, { ...options, onResult }); + + for (let item of items) { + if (!matches.includes(item)) { + result.add(item); + } + } + return [...result]; +}; + +/** + * Returns true if the given `string` contains the given pattern. Similar + * to [.isMatch](#isMatch) but the pattern can match any part of the string. + * + * ```js + * var mm = require('micromatch'); + * // mm.contains(string, pattern[, options]); + * + * console.log(mm.contains('aa/bb/cc', '*b')); + * //=> true + * console.log(mm.contains('aa/bb/cc', '*d')); + * //=> false + * ``` + * @param {String} `str` The string to match. + * @param {String|Array} `patterns` Glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if the patter matches any part of `str`. + * @api public + */ + +micromatch.contains = (str, pattern, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + + if (Array.isArray(pattern)) { + return pattern.some(p => micromatch.contains(str, p, options)); + } + + if (typeof pattern === 'string') { + if (isEmptyString(str) || isEmptyString(pattern)) { + return false; + } + + if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) { + return true; + } + } + + return micromatch.isMatch(str, pattern, { ...options, contains: true }); +}; + +/** + * Filter the keys of the given object with the given `glob` pattern + * and `options`. Does not attempt to match nested keys. If you need this feature, + * use [glob-object][] instead. + * + * ```js + * const mm = require('micromatch'); + * // mm.matchKeys(object, patterns[, options]); + * + * const obj = { aa: 'a', ab: 'b', ac: 'c' }; + * console.log(mm.matchKeys(obj, '*b')); + * //=> { ab: 'b' } + * ``` + * @param {Object} `object` The object with keys to filter. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Object} Returns an object with only keys that match the given patterns. + * @api public + */ + +micromatch.matchKeys = (obj, patterns, options) => { + if (!utils.isObject(obj)) { + throw new TypeError('Expected the first argument to be an object'); + } + let keys = micromatch(Object.keys(obj), patterns, options); + let res = {}; + for (let key of keys) res[key] = obj[key]; + return res; +}; + +/** + * Returns true if some of the strings in the given `list` match any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.some(list, patterns[, options]); + * + * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // true + * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.some = (list, patterns, options) => { + let items = [].concat(list); + + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (items.some(item => isMatch(item))) { + return true; + } + } + return false; +}; + +/** + * Returns true if every string in the given `list` matches + * any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.every(list, patterns[, options]); + * + * console.log(mm.every('foo.js', ['foo.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // false + * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.every = (list, patterns, options) => { + let items = [].concat(list); + + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (!items.every(item => isMatch(item))) { + return false; + } + } + return true; +}; + +/** + * Returns true if **all** of the given `patterns` match + * the specified string. + * + * ```js + * const mm = require('micromatch'); + * // mm.all(string, patterns[, options]); + * + * console.log(mm.all('foo.js', ['foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); + * // false + * + * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); + * // true + * ``` + * @param {String|Array} `str` The string to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.all = (str, patterns, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + + return [].concat(patterns).every(p => picomatch(p, options)(str)); +}; + +/** + * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. + * + * ```js + * const mm = require('micromatch'); + * // mm.capture(pattern, string[, options]); + * + * console.log(mm.capture('test/*.js', 'test/foo.js')); + * //=> ['foo'] + * console.log(mm.capture('test/*.js', 'foo/bar.css')); + * //=> null + * ``` + * @param {String} `glob` Glob pattern to use for matching. + * @param {String} `input` String to match + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`. + * @api public + */ + +micromatch.capture = (glob, input, options) => { + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(String(glob), { ...options, capture: true }); + let match = regex.exec(posix ? utils.toPosixSlashes(input) : input); + + if (match) { + return match.slice(1).map(v => v === void 0 ? '' : v); + } +}; + +/** + * Create a regular expression from the given glob `pattern`. + * + * ```js + * const mm = require('micromatch'); + * // mm.makeRe(pattern[, options]); + * + * console.log(mm.makeRe('*.js')); + * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ + * ``` + * @param {String} `pattern` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ + +micromatch.makeRe = (...args) => picomatch.makeRe(...args); + +/** + * Scan a glob pattern to separate the pattern into segments. Used + * by the [split](#split) method. + * + * ```js + * const mm = require('micromatch'); + * const state = mm.scan(pattern[, options]); + * ``` + * @param {String} `pattern` + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ + +micromatch.scan = (...args) => picomatch.scan(...args); + +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const mm = require('micromatch'); + * const state = mm(pattern[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as regex source string. + * @api public + */ + +micromatch.parse = (patterns, options) => { + let res = []; + for (let pattern of [].concat(patterns || [])) { + for (let str of braces(String(pattern), options)) { + res.push(picomatch.parse(str, options)); + } + } + return res; +}; + +/** + * Process the given brace `pattern`. + * + * ```js + * const { braces } = require('micromatch'); + * console.log(braces('foo/{a,b,c}/bar')); + * //=> [ 'foo/(a|b|c)/bar' ] + * + * console.log(braces('foo/{a,b,c}/bar', { expand: true })); + * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ] + * ``` + * @param {String} `pattern` String with brace pattern to process. + * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. + * @return {Array} + * @api public + */ + +micromatch.braces = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) { + return [pattern]; + } + return braces(pattern, options); +}; + +/** + * Expand braces + */ + +micromatch.braceExpand = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + return micromatch.braces(pattern, { ...options, expand: true }); +}; + +/** + * Expose micromatch + */ + +module.exports = micromatch; + + +/***/ }), +/* 190 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const stringify = __webpack_require__(191); +const compile = __webpack_require__(193); +const expand = __webpack_require__(197); +const parse = __webpack_require__(198); + +/** + * Expand the given pattern or create a regex-compatible string. + * + * ```js + * const braces = require('braces'); + * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] + * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {String} + * @api public + */ + +const braces = (input, options = {}) => { + let output = []; + + if (Array.isArray(input)) { + for (let pattern of input) { + let result = braces.create(pattern, options); + if (Array.isArray(result)) { + output.push(...result); + } else { + output.push(result); + } + } + } else { + output = [].concat(braces.create(input, options)); + } + + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; + } + return output; +}; + +/** + * Parse the given `str` with the given `options`. + * + * ```js + * // braces.parse(pattern, [, options]); + * const ast = braces.parse('a/{b,c}/d'); + * console.log(ast); + * ``` + * @param {String} pattern Brace pattern to parse + * @param {Object} options + * @return {Object} Returns an AST + * @api public + */ + +braces.parse = (input, options = {}) => parse(input, options); + +/** + * Creates a braces string from an AST, or an AST node. + * + * ```js + * const braces = require('braces'); + * let ast = braces.parse('foo/{a,b}/bar'); + * console.log(stringify(ast.nodes[2])); //=> '{a,b}' + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.stringify = (input, options = {}) => { + if (typeof input === 'string') { + return stringify(braces.parse(input, options), options); + } + return stringify(input, options); +}; + +/** + * Compiles a brace pattern into a regex-compatible, optimized string. + * This method is called by the main [braces](#braces) function by default. + * + * ```js + * const braces = require('braces'); + * console.log(braces.compile('a/{b,c}/d')); + * //=> ['a/(b|c)/d'] + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.compile = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + return compile(input, options); +}; + +/** + * Expands a brace pattern into an array. This method is called by the + * main [braces](#braces) function when `options.expand` is true. Before + * using this method it's recommended that you read the [performance notes](#performance)) + * and advantages of using [.compile](#compile) instead. + * + * ```js + * const braces = require('braces'); + * console.log(braces.expand('a/{b,c}/d')); + * //=> ['a/b/d', 'a/c/d']; + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.expand = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + + let result = expand(input, options); + + // filter out empty strings if specified + if (options.noempty === true) { + result = result.filter(Boolean); + } + + // filter out duplicates if specified + if (options.nodupes === true) { + result = [...new Set(result)]; + } + + return result; +}; + +/** + * Processes a brace pattern and returns either an expanded array + * (if `options.expand` is true), a highly optimized regex-compatible string. + * This method is called by the main [braces](#braces) function. + * + * ```js + * const braces = require('braces'); + * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) + * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.create = (input, options = {}) => { + if (input === '' || input.length < 3) { + return [input]; + } + + return options.expand !== true + ? braces.compile(input, options) + : braces.expand(input, options); +}; + +/** + * Expose "braces" + */ + +module.exports = braces; + + +/***/ }), +/* 191 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(192); + +module.exports = (ast, options = {}) => { + let stringify = (node, parent = {}) => { + let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let output = ''; + + if (node.value) { + if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { + return '\\' + node.value; + } + return node.value; + } + + if (node.value) { + return node.value; + } + + if (node.nodes) { + for (let child of node.nodes) { + output += stringify(child); + } + } + return output; + }; + + return stringify(ast); +}; + + + +/***/ }), +/* 192 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +exports.isInteger = num => { + if (typeof num === 'number') { + return Number.isInteger(num); + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isInteger(Number(num)); + } + return false; +}; + +/** + * Find a node of the given type + */ + +exports.find = (node, type) => node.nodes.find(node => node.type === type); + +/** + * Find a node of the given type + */ + +exports.exceedsLimit = (min, max, step = 1, limit) => { + if (limit === false) return false; + if (!exports.isInteger(min) || !exports.isInteger(max)) return false; + return ((Number(max) - Number(min)) / Number(step)) >= limit; +}; + +/** + * Escape the given node with '\\' before node.value + */ + +exports.escapeNode = (block, n = 0, type) => { + let node = block.nodes[n]; + if (!node) return; + + if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { + if (node.escaped !== true) { + node.value = '\\' + node.value; + node.escaped = true; + } + } +}; + +/** + * Returns true if the given brace node should be enclosed in literal braces + */ + +exports.encloseBrace = node => { + if (node.type !== 'brace') return false; + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; + return true; + } + return false; +}; + +/** + * Returns true if a brace node is invalid. + */ + +exports.isInvalidBrace = block => { + if (block.type !== 'brace') return false; + if (block.invalid === true || block.dollar) return true; + if ((block.commas >> 0 + block.ranges >> 0) === 0) { + block.invalid = true; + return true; + } + if (block.open !== true || block.close !== true) { + block.invalid = true; + return true; + } + return false; +}; + +/** + * Returns true if a node is an open or close node + */ + +exports.isOpenOrClose = node => { + if (node.type === 'open' || node.type === 'close') { + return true; + } + return node.open === true || node.close === true; +}; + +/** + * Reduce an array of text nodes. + */ + +exports.reduce = nodes => nodes.reduce((acc, node) => { + if (node.type === 'text') acc.push(node.value); + if (node.type === 'range') node.type = 'text'; + return acc; +}, []); + +/** + * Flatten an array + */ + +exports.flatten = (...args) => { + const result = []; + const flat = arr => { + for (let i = 0; i < arr.length; i++) { + let ele = arr[i]; + Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); + } + return result; + }; + flat(args); + return result; +}; + + +/***/ }), +/* 193 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const fill = __webpack_require__(194); +const utils = __webpack_require__(192); + +const compile = (ast, options = {}) => { + let walk = (node, parent = {}) => { + let invalidBlock = utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let invalid = invalidBlock === true || invalidNode === true; + let prefix = options.escapeInvalid === true ? '\\' : ''; + let output = ''; + + if (node.isOpen === true) { + return prefix + node.value; + } + if (node.isClose === true) { + return prefix + node.value; + } + + if (node.type === 'open') { + return invalid ? (prefix + node.value) : '('; + } + + if (node.type === 'close') { + return invalid ? (prefix + node.value) : ')'; + } + + if (node.type === 'comma') { + return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); + } + + if (node.value) { + return node.value; + } + + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + let range = fill(...args, { ...options, wrap: false, toRegex: true }); + + if (range.length !== 0) { + return args.length > 1 && range.length > 1 ? `(${range})` : range; + } + } + + if (node.nodes) { + for (let child of node.nodes) { + output += walk(child, node); + } + } + return output; + }; + + return walk(ast); +}; + +module.exports = compile; + + +/***/ }), +/* 194 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +const util = __webpack_require__(29); +const toRegexRange = __webpack_require__(195); + +const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); + +const transform = toNumber => { + return value => toNumber === true ? Number(value) : String(value); +}; + +const isValidValue = value => { + return typeof value === 'number' || (typeof value === 'string' && value !== ''); +}; + +const isNumber = num => Number.isInteger(+num); + +const zeros = input => { + let value = `${input}`; + let index = -1; + if (value[0] === '-') value = value.slice(1); + if (value === '0') return false; + while (value[++index] === '0'); + return index > 0; +}; + +const stringify = (start, end, options) => { + if (typeof start === 'string' || typeof end === 'string') { + return true; + } + return options.stringify === true; +}; + +const pad = (input, maxLength, toNumber) => { + if (maxLength > 0) { + let dash = input[0] === '-' ? '-' : ''; + if (dash) input = input.slice(1); + input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); + } + if (toNumber === false) { + return String(input); + } + return input; +}; + +const toMaxLen = (input, maxLength) => { + let negative = input[0] === '-' ? '-' : ''; + if (negative) { + input = input.slice(1); + maxLength--; + } + while (input.length < maxLength) input = '0' + input; + return negative ? ('-' + input) : input; +}; + +const toSequence = (parts, options) => { + parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + + let prefix = options.capture ? '' : '?:'; + let positives = ''; + let negatives = ''; + let result; + + if (parts.positives.length) { + positives = parts.positives.join('|'); + } + + if (parts.negatives.length) { + negatives = `-(${prefix}${parts.negatives.join('|')})`; + } + + if (positives && negatives) { + result = `${positives}|${negatives}`; + } else { + result = positives || negatives; + } + + if (options.wrap) { + return `(${prefix}${result})`; + } + + return result; +}; + +const toRange = (a, b, isNumbers, options) => { + if (isNumbers) { + return toRegexRange(a, b, { wrap: false, ...options }); + } + + let start = String.fromCharCode(a); + if (a === b) return start; + + let stop = String.fromCharCode(b); + return `[${start}-${stop}]`; +}; + +const toRegex = (start, end, options) => { + if (Array.isArray(start)) { + let wrap = options.wrap === true; + let prefix = options.capture ? '' : '?:'; + return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); + } + return toRegexRange(start, end, options); +}; + +const rangeError = (...args) => { + return new RangeError('Invalid range arguments: ' + util.inspect(...args)); +}; + +const invalidRange = (start, end, options) => { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; +}; + +const invalidStep = (step, options) => { + if (options.strictRanges === true) { + throw new TypeError(`Expected step "${step}" to be a number`); + } + return []; +}; + +const fillNumbers = (start, end, step = 1, options = {}) => { + let a = Number(start); + let b = Number(end); + + if (!Number.isInteger(a) || !Number.isInteger(b)) { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; + } + + // fix negative zero + if (a === 0) a = 0; + if (b === 0) b = 0; + + let descending = a > b; + let startString = String(start); + let endString = String(end); + let stepString = String(step); + step = Math.max(Math.abs(step), 1); + + let padded = zeros(startString) || zeros(endString) || zeros(stepString); + let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; + let toNumber = padded === false && stringify(start, end, options) === false; + let format = options.transform || transform(toNumber); + + if (options.toRegex && step === 1) { + return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); + } + + let parts = { negatives: [], positives: [] }; + let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); + let range = []; + let index = 0; + + while (descending ? a >= b : a <= b) { + if (options.toRegex === true && step > 1) { + push(a); + } else { + range.push(pad(format(a, index), maxLen, toNumber)); + } + a = descending ? a - step : a + step; + index++; + } + + if (options.toRegex === true) { + return step > 1 + ? toSequence(parts, options) + : toRegex(range, null, { wrap: false, ...options }); + } + + return range; +}; + +const fillLetters = (start, end, step = 1, options = {}) => { + if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { + return invalidRange(start, end, options); + } + + + let format = options.transform || (val => String.fromCharCode(val)); + let a = `${start}`.charCodeAt(0); + let b = `${end}`.charCodeAt(0); + + let descending = a > b; + let min = Math.min(a, b); + let max = Math.max(a, b); + + if (options.toRegex && step === 1) { + return toRange(min, max, false, options); + } + + let range = []; + let index = 0; + + while (descending ? a >= b : a <= b) { + range.push(format(a, index)); + a = descending ? a - step : a + step; + index++; + } + + if (options.toRegex === true) { + return toRegex(range, null, { wrap: false, options }); + } + + return range; +}; + +const fill = (start, end, step, options = {}) => { + if (end == null && isValidValue(start)) { + return [start]; + } + + if (!isValidValue(start) || !isValidValue(end)) { + return invalidRange(start, end, options); + } + + if (typeof step === 'function') { + return fill(start, end, 1, { transform: step }); + } + + if (isObject(step)) { + return fill(start, end, 0, step); + } + + let opts = { ...options }; + if (opts.capture === true) opts.wrap = true; + step = step || opts.step || 1; + + if (!isNumber(step)) { + if (step != null && !isObject(step)) return invalidStep(step, opts); + return fill(start, end, 1, step); + } + + if (isNumber(start) && isNumber(end)) { + return fillNumbers(start, end, step, opts); + } + + return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); +}; + +module.exports = fill; + + +/***/ }), +/* 195 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ + + + +const isNumber = __webpack_require__(196); + +const toRegexRange = (min, max, options) => { + if (isNumber(min) === false) { + throw new TypeError('toRegexRange: expected the first argument to be a number'); + } + + if (max === void 0 || min === max) { + return String(min); + } + + if (isNumber(max) === false) { + throw new TypeError('toRegexRange: expected the second argument to be a number.'); + } + + let opts = { relaxZeros: true, ...options }; + if (typeof opts.strictZeros === 'boolean') { + opts.relaxZeros = opts.strictZeros === false; + } + + let relax = String(opts.relaxZeros); + let shorthand = String(opts.shorthand); + let capture = String(opts.capture); + let wrap = String(opts.wrap); + let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; + + if (toRegexRange.cache.hasOwnProperty(cacheKey)) { + return toRegexRange.cache[cacheKey].result; + } + + let a = Math.min(min, max); + let b = Math.max(min, max); + + if (Math.abs(a - b) === 1) { + let result = min + '|' + max; + if (opts.capture) { + return `(${result})`; + } + if (opts.wrap === false) { + return result; + } + return `(?:${result})`; + } + + let isPadded = hasPadding(min) || hasPadding(max); + let state = { min, max, a, b }; + let positives = []; + let negatives = []; + + if (isPadded) { + state.isPadded = isPadded; + state.maxLen = String(state.max).length; + } + + if (a < 0) { + let newMin = b < 0 ? Math.abs(b) : 1; + negatives = splitToPatterns(newMin, Math.abs(a), state, opts); + a = state.a = 0; + } + + if (b >= 0) { + positives = splitToPatterns(a, b, state, opts); + } + + state.negatives = negatives; + state.positives = positives; + state.result = collatePatterns(negatives, positives, opts); + + if (opts.capture === true) { + state.result = `(${state.result})`; + } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { + state.result = `(?:${state.result})`; + } + + toRegexRange.cache[cacheKey] = state; + return state.result; +}; + +function collatePatterns(neg, pos, options) { + let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; + let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; + let intersected = filterPatterns(neg, pos, '-?', true, options) || []; + let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); + return subpatterns.join('|'); +} + +function splitToRanges(min, max) { + let nines = 1; + let zeros = 1; + + let stop = countNines(min, nines); + let stops = new Set([max]); + + while (min <= stop && stop <= max) { + stops.add(stop); + nines += 1; + stop = countNines(min, nines); + } + + stop = countZeros(max + 1, zeros) - 1; + + while (min < stop && stop <= max) { + stops.add(stop); + zeros += 1; + stop = countZeros(max + 1, zeros) - 1; + } + + stops = [...stops]; + stops.sort(compare); + return stops; +} + +/** + * Convert a range to a regex pattern + * @param {Number} `start` + * @param {Number} `stop` + * @return {String} + */ + +function rangeToPattern(start, stop, options) { + if (start === stop) { + return { pattern: start, count: [], digits: 0 }; + } + + let zipped = zip(start, stop); + let digits = zipped.length; + let pattern = ''; + let count = 0; + + for (let i = 0; i < digits; i++) { + let [startDigit, stopDigit] = zipped[i]; + + if (startDigit === stopDigit) { + pattern += startDigit; + + } else if (startDigit !== '0' || stopDigit !== '9') { + pattern += toCharacterClass(startDigit, stopDigit, options); + + } else { + count++; + } + } + + if (count) { + pattern += options.shorthand === true ? '\\d' : '[0-9]'; + } + + return { pattern, count: [count], digits }; +} + +function splitToPatterns(min, max, tok, options) { + let ranges = splitToRanges(min, max); + let tokens = []; + let start = min; + let prev; + + for (let i = 0; i < ranges.length; i++) { + let max = ranges[i]; + let obj = rangeToPattern(String(start), String(max), options); + let zeros = ''; + + if (!tok.isPadded && prev && prev.pattern === obj.pattern) { + if (prev.count.length > 1) { + prev.count.pop(); + } + + prev.count.push(obj.count[0]); + prev.string = prev.pattern + toQuantifier(prev.count); + start = max + 1; + continue; + } + + if (tok.isPadded) { + zeros = padZeros(max, tok, options); + } + + obj.string = zeros + obj.pattern + toQuantifier(obj.count); + tokens.push(obj); + start = max + 1; + prev = obj; + } + + return tokens; +} + +function filterPatterns(arr, comparison, prefix, intersection, options) { + let result = []; + + for (let ele of arr) { + let { string } = ele; + + // only push if _both_ are negative... + if (!intersection && !contains(comparison, 'string', string)) { + result.push(prefix + string); + } + + // or _both_ are positive + if (intersection && contains(comparison, 'string', string)) { + result.push(prefix + string); + } + } + return result; +} + +/** + * Zip strings + */ + +function zip(a, b) { + let arr = []; + for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); + return arr; +} + +function compare(a, b) { + return a > b ? 1 : b > a ? -1 : 0; +} + +function contains(arr, key, val) { + return arr.some(ele => ele[key] === val); +} + +function countNines(min, len) { + return Number(String(min).slice(0, -len) + '9'.repeat(len)); +} + +function countZeros(integer, zeros) { + return integer - (integer % Math.pow(10, zeros)); +} + +function toQuantifier(digits) { + let [start = 0, stop = ''] = digits; + if (stop || start > 1) { + return `{${start + (stop ? ',' + stop : '')}}`; + } + return ''; +} + +function toCharacterClass(a, b, options) { + return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; +} + +function hasPadding(str) { + return /^-?(0+)\d/.test(str); +} + +function padZeros(value, tok, options) { + if (!tok.isPadded) { + return value; + } + + let diff = Math.abs(tok.maxLen - String(value).length); + let relax = options.relaxZeros !== false; + + switch (diff) { + case 0: + return ''; + case 1: + return relax ? '0?' : '0'; + case 2: + return relax ? '0{0,2}' : '00'; + default: { + return relax ? `0{0,${diff}}` : `0{${diff}}`; + } + } +} + +/** + * Cache + */ + +toRegexRange.cache = {}; +toRegexRange.clearCache = () => (toRegexRange.cache = {}); + +/** + * Expose `toRegexRange` + */ + +module.exports = toRegexRange; + + +/***/ }), +/* 196 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ + + + +module.exports = function(num) { + if (typeof num === 'number') { + return num - num === 0; + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); + } + return false; +}; + + +/***/ }), +/* 197 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const fill = __webpack_require__(194); +const stringify = __webpack_require__(191); +const utils = __webpack_require__(192); + +const append = (queue = '', stash = '', enclose = false) => { + let result = []; + + queue = [].concat(queue); + stash = [].concat(stash); + + if (!stash.length) return queue; + if (!queue.length) { + return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; + } + + for (let item of queue) { + if (Array.isArray(item)) { + for (let value of item) { + result.push(append(value, stash, enclose)); + } + } else { + for (let ele of stash) { + if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; + result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); + } + } + } + return utils.flatten(result); +}; + +const expand = (ast, options = {}) => { + let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; + + let walk = (node, parent = {}) => { + node.queue = []; + + let p = parent; + let q = parent.queue; + + while (p.type !== 'brace' && p.type !== 'root' && p.parent) { + p = p.parent; + q = p.queue; + } + + if (node.invalid || node.dollar) { + q.push(append(q.pop(), stringify(node, options))); + return; + } + + if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { + q.push(append(q.pop(), ['{}'])); + return; + } + + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + + if (utils.exceedsLimit(...args, options.step, rangeLimit)) { + throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); + } + + let range = fill(...args, options); + if (range.length === 0) { + range = stringify(node, options); + } + + q.push(append(q.pop(), range)); + node.nodes = []; + return; + } + + let enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; + + while (block.type !== 'brace' && block.type !== 'root' && block.parent) { + block = block.parent; + queue = block.queue; + } + + for (let i = 0; i < node.nodes.length; i++) { + let child = node.nodes[i]; + + if (child.type === 'comma' && node.type === 'brace') { + if (i === 1) queue.push(''); + queue.push(''); + continue; + } + + if (child.type === 'close') { + q.push(append(q.pop(), queue, enclose)); + continue; + } + + if (child.value && child.type !== 'open') { + queue.push(append(queue.pop(), child.value)); + continue; + } + + if (child.nodes) { + walk(child, node); + } + } + + return queue; + }; + + return utils.flatten(walk(ast)); +}; + +module.exports = expand; + + +/***/ }), +/* 198 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const stringify = __webpack_require__(191); + +/** + * Constants + */ + +const { + MAX_LENGTH, + CHAR_BACKSLASH, /* \ */ + CHAR_BACKTICK, /* ` */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_RIGHT_SQUARE_BRACKET, /* ] */ + CHAR_DOUBLE_QUOTE, /* " */ + CHAR_SINGLE_QUOTE, /* ' */ + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE +} = __webpack_require__(199); + +/** + * parse + */ + +const parse = (input, options = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + let opts = options || {}; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); + } + + let ast = { type: 'root', input, nodes: [] }; + let stack = [ast]; + let block = ast; + let prev = ast; + let brackets = 0; + let length = input.length; + let index = 0; + let depth = 0; + let value; + let memo = {}; + + /** + * Helpers + */ + + const advance = () => input[index++]; + const push = node => { + if (node.type === 'text' && prev.type === 'dot') { + prev.type = 'text'; + } + + if (prev && prev.type === 'text' && node.type === 'text') { + prev.value += node.value; + return; + } + + block.nodes.push(node); + node.parent = block; + node.prev = prev; + prev = node; + return node; + }; + + push({ type: 'bos' }); + + while (index < length) { + block = stack[stack.length - 1]; + value = advance(); + + /** + * Invalid chars + */ + + if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { + continue; + } + + /** + * Escaped chars + */ + + if (value === CHAR_BACKSLASH) { + push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); + continue; + } + + /** + * Right square bracket (literal): ']' + */ + + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: 'text', value: '\\' + value }); + continue; + } + + /** + * Left square bracket: '[' + */ + + if (value === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + + let closed = true; + let next; + + while (index < length && (next = advance())) { + value += next; + + if (next === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + continue; + } + + if (next === CHAR_BACKSLASH) { + value += advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + brackets--; + + if (brackets === 0) { + break; + } + } + } + + push({ type: 'text', value }); + continue; + } + + /** + * Parentheses + */ + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: 'paren', nodes: [] }); + stack.push(block); + push({ type: 'text', value }); + continue; + } + + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== 'paren') { + push({ type: 'text', value }); + continue; + } + block = stack.pop(); + push({ type: 'text', value }); + block = stack[stack.length - 1]; + continue; + } + + /** + * Quotes: '|"|` + */ + + if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { + let open = value; + let next; + + if (options.keepQuotes !== true) { + value = ''; + } + + while (index < length && (next = advance())) { + if (next === CHAR_BACKSLASH) { + value += next + advance(); + continue; + } + + if (next === open) { + if (options.keepQuotes === true) value += next; + break; + } + + value += next; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Left curly brace: '{' + */ + + if (value === CHAR_LEFT_CURLY_BRACE) { + depth++; + + let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; + let brace = { + type: 'brace', + open: true, + close: false, + dollar, + depth, + commas: 0, + ranges: 0, + nodes: [] + }; + + block = push(brace); + stack.push(block); + push({ type: 'open', value }); + continue; + } + + /** + * Right curly brace: '}' + */ + + if (value === CHAR_RIGHT_CURLY_BRACE) { + if (block.type !== 'brace') { + push({ type: 'text', value }); + continue; + } + + let type = 'close'; + block = stack.pop(); + block.close = true; + + push({ type, value }); + depth--; + + block = stack[stack.length - 1]; + continue; + } + + /** + * Comma: ',' + */ + + if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + let open = block.nodes.shift(); + block.nodes = [open, { type: 'text', value: stringify(block) }]; + } + + push({ type: 'comma', value }); + block.commas++; + continue; + } + + /** + * Dot: '.' + */ + + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { + let siblings = block.nodes; + + if (depth === 0 || siblings.length === 0) { + push({ type: 'text', value }); + continue; + } + + if (prev.type === 'dot') { + block.range = []; + prev.value += value; + prev.type = 'range'; + + if (block.nodes.length !== 3 && block.nodes.length !== 5) { + block.invalid = true; + block.ranges = 0; + prev.type = 'text'; + continue; + } + + block.ranges++; + block.args = []; + continue; + } + + if (prev.type === 'range') { + siblings.pop(); + + let before = siblings[siblings.length - 1]; + before.value += prev.value + value; + prev = before; + block.ranges--; + continue; + } + + push({ type: 'dot', value }); + continue; + } + + /** + * Text + */ + + push({ type: 'text', value }); + } + + // Mark imbalanced braces and brackets as invalid + do { + block = stack.pop(); + + if (block.type !== 'root') { + block.nodes.forEach(node => { + if (!node.nodes) { + if (node.type === 'open') node.isOpen = true; + if (node.type === 'close') node.isClose = true; + if (!node.nodes) node.type = 'text'; + node.invalid = true; + } + }); + + // get the location of the block on parent.nodes (block's siblings) + let parent = stack[stack.length - 1]; + let index = parent.nodes.indexOf(block); + // replace the (invalid) block with it's nodes + parent.nodes.splice(index, 1, ...block.nodes); + } + } while (stack.length > 0); + + push({ type: 'eos' }); + return ast; +}; + +module.exports = parse; + + +/***/ }), +/* 199 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = { + MAX_LENGTH: 1024 * 64, + + // Digits + CHAR_0: '0', /* 0 */ + CHAR_9: '9', /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 'A', /* A */ + CHAR_LOWERCASE_A: 'a', /* a */ + CHAR_UPPERCASE_Z: 'Z', /* Z */ + CHAR_LOWERCASE_Z: 'z', /* z */ + + CHAR_LEFT_PARENTHESES: '(', /* ( */ + CHAR_RIGHT_PARENTHESES: ')', /* ) */ + + CHAR_ASTERISK: '*', /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: '&', /* & */ + CHAR_AT: '@', /* @ */ + CHAR_BACKSLASH: '\\', /* \ */ + CHAR_BACKTICK: '`', /* ` */ + CHAR_CARRIAGE_RETURN: '\r', /* \r */ + CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ + CHAR_COLON: ':', /* : */ + CHAR_COMMA: ',', /* , */ + CHAR_DOLLAR: '$', /* . */ + CHAR_DOT: '.', /* . */ + CHAR_DOUBLE_QUOTE: '"', /* " */ + CHAR_EQUAL: '=', /* = */ + CHAR_EXCLAMATION_MARK: '!', /* ! */ + CHAR_FORM_FEED: '\f', /* \f */ + CHAR_FORWARD_SLASH: '/', /* / */ + CHAR_HASH: '#', /* # */ + CHAR_HYPHEN_MINUS: '-', /* - */ + CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ + CHAR_LEFT_CURLY_BRACE: '{', /* { */ + CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ + CHAR_LINE_FEED: '\n', /* \n */ + CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ + CHAR_PERCENT: '%', /* % */ + CHAR_PLUS: '+', /* + */ + CHAR_QUESTION_MARK: '?', /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ + CHAR_RIGHT_CURLY_BRACE: '}', /* } */ + CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ + CHAR_SEMICOLON: ';', /* ; */ + CHAR_SINGLE_QUOTE: '\'', /* ' */ + CHAR_SPACE: ' ', /* */ + CHAR_TAB: '\t', /* \t */ + CHAR_UNDERSCORE: '_', /* _ */ + CHAR_VERTICAL_LINE: '|', /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ +}; + + +/***/ }), +/* 200 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = __webpack_require__(201); + + +/***/ }), +/* 201 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const scan = __webpack_require__(202); +const parse = __webpack_require__(205); +const utils = __webpack_require__(203); + +/** + * Creates a matcher function from one or more glob patterns. The + * returned function takes a string to match as its first argument, + * and returns true if the string is a match. The returned matcher + * function also takes a boolean as the second argument that, when true, + * returns an object with additional information. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch(glob[, options]); + * + * const isMatch = picomatch('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @name picomatch + * @param {String|Array} `globs` One or more glob patterns. + * @param {Object=} `options` + * @return {Function=} Returns a matcher function. + * @api public + */ + +const picomatch = (glob, options, returnState = false) => { + if (Array.isArray(glob)) { + let fns = glob.map(input => picomatch(input, options, returnState)); + return str => { + for (let isMatch of fns) { + let state = isMatch(str); + if (state) return state; + } + return false; + }; + } + + if (typeof glob !== 'string' || glob === '') { + throw new TypeError('Expected pattern to be a non-empty string'); + } + + let opts = options || {}; + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(glob, options, false, true); + let state = regex.state; + delete regex.state; + + let isIgnored = () => false; + if (opts.ignore) { + let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; + isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); + } + + const matcher = (input, returnObject = false) => { + let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); + let result = { glob, state, regex, posix, input, output, match, isMatch }; + + if (typeof opts.onResult === 'function') { + opts.onResult(result); + } + + if (isMatch === false) { + result.isMatch = false; + return returnObject ? result : false; + } + + if (isIgnored(input)) { + if (typeof opts.onIgnore === 'function') { + opts.onIgnore(result); + } + result.isMatch = false; + return returnObject ? result : false; + } + + if (typeof opts.onMatch === 'function') { + opts.onMatch(result); + } + return returnObject ? result : true; + }; + + if (returnState) { + matcher.state = state; + } + + return matcher; +}; + +/** + * Test `input` with the given `regex`. This is used by the main + * `picomatch()` function to test the input string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.test(input, regex[, options]); + * + * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); + * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } + * ``` + * @param {String} `input` String to test. + * @param {RegExp} `regex` + * @return {Object} Returns an object with matching info. + * @api public + */ + +picomatch.test = (input, regex, options, { glob, posix } = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected input to be a string'); + } + + if (input === '') { + return { isMatch: false, output: '' }; + } + + let opts = options || {}; + let format = opts.format || (posix ? utils.toPosixSlashes : null); + let match = input === glob; + let output = (match && format) ? format(input) : input; + + if (match === false) { + output = format ? format(input) : input; + match = output === glob; + } + + if (match === false || opts.capture === true) { + if (opts.matchBase === true || opts.basename === true) { + match = picomatch.matchBase(input, regex, options, posix); + } else { + match = regex.exec(output); + } + } + + return { isMatch: !!match, match, output }; +}; + +/** + * Match the basename of a filepath. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.matchBase(input, glob[, options]); + * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true + * ``` + * @param {String} `input` String to test. + * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). + * @return {Boolean} + * @api public + */ + +picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { + let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); + return regex.test(path.basename(input)); +}; + +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.isMatch(string, patterns[, options]); + * + * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String|Array} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const picomatch = require('picomatch'); + * const result = picomatch.parse(glob[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as a regex source string. + * @api public + */ + +picomatch.parse = (glob, options) => parse(glob, options); + +/** + * Scan a glob pattern to separate the pattern into segments. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.scan(input[, options]); + * + * const result = picomatch.scan('!./foo/*.js'); + * console.log(result); + * // { prefix: '!./', + * // input: '!./foo/*.js', + * // base: 'foo', + * // glob: '*.js', + * // negated: true, + * // isGlob: true } + * ``` + * @param {String} `input` Glob pattern to scan. + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ + +picomatch.scan = (input, options) => scan(input, options); + +/** + * Create a regular expression from a glob pattern. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.makeRe(input[, options]); + * + * console.log(picomatch.makeRe('*.js')); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `input` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ + +picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { + if (!input || typeof input !== 'string') { + throw new TypeError('Expected a non-empty string'); + } + + let opts = options || {}; + let prepend = opts.contains ? '' : '^'; + let append = opts.contains ? '' : '$'; + let state = { negated: false, fastpaths: true }; + let prefix = ''; + let output; + + if (input.startsWith('./')) { + input = input.slice(2); + prefix = state.prefix = './'; + } + + if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { + output = parse.fastpaths(input, options); + } + + if (output === void 0) { + state = picomatch.parse(input, options); + state.prefix = prefix + (state.prefix || ''); + output = state.output; + } + + if (returnOutput === true) { + return output; + } + + let source = `${prepend}(?:${output})${append}`; + if (state && state.negated === true) { + source = `^(?!${source}).*$`; + } + + let regex = picomatch.toRegex(source, options); + if (returnState === true) { + regex.state = state; + } + + return regex; +}; + +/** + * Create a regular expression from the given regex source string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.toRegex(source[, options]); + * + * const { output } = picomatch.parse('*.js'); + * console.log(picomatch.toRegex(output)); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `source` Regular expression source string. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ + +picomatch.toRegex = (source, options) => { + try { + let opts = options || {}; + return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); + } catch (err) { + if (options && options.debug === true) throw err; + return /$^/; + } +}; + +/** + * Picomatch constants. + * @return {Object} + */ + +picomatch.constants = __webpack_require__(204); + +/** + * Expose "picomatch" + */ + +module.exports = picomatch; + + +/***/ }), +/* 202 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(203); + +const { + CHAR_ASTERISK, /* * */ + CHAR_AT, /* @ */ + CHAR_BACKWARD_SLASH, /* \ */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_EXCLAMATION_MARK, /* ! */ + CHAR_FORWARD_SLASH, /* / */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_PLUS, /* + */ + CHAR_QUESTION_MARK, /* ? */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_RIGHT_SQUARE_BRACKET /* ] */ +} = __webpack_require__(204); + +const isPathSeparator = code => { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +}; + +/** + * Quickly scans a glob pattern and returns an object with a handful of + * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), + * `glob` (the actual pattern), and `negated` (true if the path starts with `!`). + * + * ```js + * const pm = require('picomatch'); + * console.log(pm.scan('foo/bar/*.js')); + * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {Object} Returns an object with tokens and regex source string. + * @api public + */ + +module.exports = (input, options) => { + let opts = options || {}; + let length = input.length - 1; + let index = -1; + let start = 0; + let lastIndex = 0; + let isGlob = false; + let backslashes = false; + let negated = false; + let braces = 0; + let prev; + let code; + + let braceEscaped = false; + + let eos = () => index >= length; + let advance = () => { + prev = code; + return input.charCodeAt(++index); + }; + + while (index < length) { + code = advance(); + let next; + + if (code === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + + if (next === CHAR_LEFT_CURLY_BRACE) { + braceEscaped = true; + } + continue; + } + + if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { + braces++; + + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + + if (next === CHAR_LEFT_CURLY_BRACE) { + braces++; + continue; + } + + if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) { + isGlob = true; + break; + } + + if (!braceEscaped && next === CHAR_COMMA) { + isGlob = true; + break; + } + + if (next === CHAR_RIGHT_CURLY_BRACE) { + braces--; + if (braces === 0) { + braceEscaped = false; + break; + } + } + } + } + + if (code === CHAR_FORWARD_SLASH) { + if (prev === CHAR_DOT && index === (start + 1)) { + start += 2; + continue; + } + + lastIndex = index + 1; + continue; + } + + if (code === CHAR_ASTERISK) { + isGlob = true; + break; + } + + if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) { + isGlob = true; + break; + } + + if (code === CHAR_LEFT_SQUARE_BRACKET) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + isGlob = true; + break; + } + } + } + + let isExtglobChar = code === CHAR_PLUS + || code === CHAR_AT + || code === CHAR_EXCLAMATION_MARK; + + if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) { + isGlob = true; + break; + } + + if (code === CHAR_EXCLAMATION_MARK && index === start) { + negated = true; + start++; + continue; + } + + if (code === CHAR_LEFT_PARENTHESES) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + + if (next === CHAR_RIGHT_PARENTHESES) { + isGlob = true; + break; + } + } + } + + if (isGlob) { + break; + } + } + + let prefix = ''; + let orig = input; + let base = input; + let glob = ''; + + if (start > 0) { + prefix = input.slice(0, start); + input = input.slice(start); + lastIndex -= start; + } + + if (base && isGlob === true && lastIndex > 0) { + base = input.slice(0, lastIndex); + glob = input.slice(lastIndex); + } else if (isGlob === true) { + base = ''; + glob = input; + } else { + base = input; + } + + if (base && base !== '' && base !== '/' && base !== input) { + if (isPathSeparator(base.charCodeAt(base.length - 1))) { + base = base.slice(0, -1); + } + } + + if (opts.unescape === true) { + if (glob) glob = utils.removeBackslashes(glob); + + if (base && backslashes === true) { + base = utils.removeBackslashes(base); + } + } + + return { prefix, input: orig, base, glob, negated, isGlob }; +}; + + +/***/ }), +/* 203 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const win32 = process.platform === 'win32'; +const { + REGEX_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_GLOBAL, + REGEX_REMOVE_BACKSLASH +} = __webpack_require__(204); + +exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); +exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); +exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); +exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); +exports.toPosixSlashes = str => str.replace(/\\/g, '/'); + +exports.removeBackslashes = str => { + return str.replace(REGEX_REMOVE_BACKSLASH, match => { + return match === '\\' ? '' : match; + }); +} + +exports.supportsLookbehinds = () => { + let segs = process.version.slice(1).split('.'); + if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) { + return true; + } + return false; +}; + +exports.isWindows = options => { + if (options && typeof options.windows === 'boolean') { + return options.windows; + } + return win32 === true || path.sep === '\\'; +}; + +exports.escapeLast = (input, char, lastIdx) => { + let idx = input.lastIndexOf(char, lastIdx); + if (idx === -1) return input; + if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); + return input.slice(0, idx) + '\\' + input.slice(idx); +}; + + +/***/ }), +/* 204 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const WIN_SLASH = '\\\\/'; +const WIN_NO_SLASH = `[^${WIN_SLASH}]`; + +/** + * Posix glob regex + */ + +const DOT_LITERAL = '\\.'; +const PLUS_LITERAL = '\\+'; +const QMARK_LITERAL = '\\?'; +const SLASH_LITERAL = '\\/'; +const ONE_CHAR = '(?=.)'; +const QMARK = '[^/]'; +const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; +const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; +const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; +const NO_DOT = `(?!${DOT_LITERAL})`; +const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; +const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; +const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; +const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; +const STAR = `${QMARK}*?`; + +const POSIX_CHARS = { + DOT_LITERAL, + PLUS_LITERAL, + QMARK_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + QMARK, + END_ANCHOR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK_NO_DOT, + STAR, + START_ANCHOR +}; + +/** + * Windows glob regex + */ + +const WINDOWS_CHARS = { + ...POSIX_CHARS, + + SLASH_LITERAL: `[${WIN_SLASH}]`, + QMARK: WIN_NO_SLASH, + STAR: `${WIN_NO_SLASH}*?`, + DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, + NO_DOT: `(?!${DOT_LITERAL})`, + NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, + NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + QMARK_NO_DOT: `[^.${WIN_SLASH}]`, + START_ANCHOR: `(?:^|[${WIN_SLASH}])`, + END_ANCHOR: `(?:[${WIN_SLASH}]|$)` +}; + +/** + * POSIX Bracket Regex + */ + +const POSIX_REGEX_SOURCE = { + alnum: 'a-zA-Z0-9', + alpha: 'a-zA-Z', + ascii: '\\x00-\\x7F', + blank: ' \\t', + cntrl: '\\x00-\\x1F\\x7F', + digit: '0-9', + graph: '\\x21-\\x7E', + lower: 'a-z', + print: '\\x20-\\x7E ', + punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', + space: ' \\t\\r\\n\\v\\f', + upper: 'A-Z', + word: 'A-Za-z0-9_', + xdigit: 'A-Fa-f0-9' +}; + +module.exports = { + MAX_LENGTH: 1024 * 64, + POSIX_REGEX_SOURCE, + + // regular expressions + REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, + REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/, + REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, + REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, + REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, + REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, + + // Replace globs with equivalent patterns to reduce parsing time. + REPLACEMENTS: { + '***': '*', + '**/**': '**', + '**/**/**': '**' + }, + + // Digits + CHAR_0: 48, /* 0 */ + CHAR_9: 57, /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 65, /* A */ + CHAR_LOWERCASE_A: 97, /* a */ + CHAR_UPPERCASE_Z: 90, /* Z */ + CHAR_LOWERCASE_Z: 122, /* z */ + + CHAR_LEFT_PARENTHESES: 40, /* ( */ + CHAR_RIGHT_PARENTHESES: 41, /* ) */ + + CHAR_ASTERISK: 42, /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: 38, /* & */ + CHAR_AT: 64, /* @ */ + CHAR_BACKWARD_SLASH: 92, /* \ */ + CHAR_CARRIAGE_RETURN: 13, /* \r */ + CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ + CHAR_COLON: 58, /* : */ + CHAR_COMMA: 44, /* , */ + CHAR_DOT: 46, /* . */ + CHAR_DOUBLE_QUOTE: 34, /* " */ + CHAR_EQUAL: 61, /* = */ + CHAR_EXCLAMATION_MARK: 33, /* ! */ + CHAR_FORM_FEED: 12, /* \f */ + CHAR_FORWARD_SLASH: 47, /* / */ + CHAR_GRAVE_ACCENT: 96, /* ` */ + CHAR_HASH: 35, /* # */ + CHAR_HYPHEN_MINUS: 45, /* - */ + CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ + CHAR_LEFT_CURLY_BRACE: 123, /* { */ + CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ + CHAR_LINE_FEED: 10, /* \n */ + CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ + CHAR_PERCENT: 37, /* % */ + CHAR_PLUS: 43, /* + */ + CHAR_QUESTION_MARK: 63, /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ + CHAR_RIGHT_CURLY_BRACE: 125, /* } */ + CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ + CHAR_SEMICOLON: 59, /* ; */ + CHAR_SINGLE_QUOTE: 39, /* ' */ + CHAR_SPACE: 32, /* */ + CHAR_TAB: 9, /* \t */ + CHAR_UNDERSCORE: 95, /* _ */ + CHAR_VERTICAL_LINE: 124, /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ + + SEP: path.sep, + + /** + * Create EXTGLOB_CHARS + */ + + extglobChars(chars) { + return { + '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, + '?': { type: 'qmark', open: '(?:', close: ')?' }, + '+': { type: 'plus', open: '(?:', close: ')+' }, + '*': { type: 'star', open: '(?:', close: ')*' }, + '@': { type: 'at', open: '(?:', close: ')' } + }; + }, + + /** + * Create GLOB_CHARS + */ + + globChars(win32) { + return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; + } +}; + + +/***/ }), +/* 205 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(203); +const constants = __webpack_require__(204); + +/** + * Constants + */ + +const { + MAX_LENGTH, + POSIX_REGEX_SOURCE, + REGEX_NON_SPECIAL_CHAR, + REGEX_SPECIAL_CHARS_BACKREF, + REPLACEMENTS +} = constants; + +/** + * Helpers + */ + +const expandRange = (args, options) => { + if (typeof options.expandRange === 'function') { + return options.expandRange(...args, options); + } + + args.sort(); + let value = `[${args.join('-')}]`; + + try { + /* eslint-disable no-new */ + new RegExp(value); + } catch (ex) { + return args.map(v => utils.escapeRegex(v)).join('..'); + } + + return value; +}; + +const negate = state => { + let count = 1; + + while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { + state.advance(); + state.start++; + count++; + } + + if (count % 2 === 0) { + return false; + } + + state.negated = true; + state.start++; + return true; +}; + +/** + * Create the message for a syntax error + */ + +const syntaxError = (type, char) => { + return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; +}; + +/** + * Parse the given input string. + * @param {String} input + * @param {Object} options + * @return {Object} + */ + +const parse = (input, options) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + input = REPLACEMENTS[input] || input; + + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + + let bos = { type: 'bos', value: '', output: opts.prepend || '' }; + let tokens = [bos]; + + let capture = opts.capture ? '' : '?:'; + let win32 = utils.isWindows(options); + + // create constants based on platform, for windows or posix + const PLATFORM_CHARS = constants.globChars(win32); + const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + + const { + DOT_LITERAL, + PLUS_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK, + QMARK_NO_DOT, + STAR, + START_ANCHOR + } = PLATFORM_CHARS; + + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + let nodot = opts.dot ? '' : NO_DOT; + let star = opts.bash === true ? globstar(opts) : STAR; + let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; + + if (opts.capture) { + star = `(${star})`; + } + + // minimatch options support + if (typeof opts.noext === 'boolean') { + opts.noextglob = opts.noext; + } + + let state = { + index: -1, + start: 0, + consumed: '', + output: '', + backtrack: false, + brackets: 0, + braces: 0, + parens: 0, + quotes: 0, + tokens + }; + + let extglobs = []; + let stack = []; + let prev = bos; + let value; + + /** + * Tokenizing helpers + */ + + const eos = () => state.index === len - 1; + const peek = state.peek = (n = 1) => input[state.index + n]; + const advance = state.advance = () => input[++state.index]; + const append = token => { + state.output += token.output != null ? token.output : token.value; + state.consumed += token.value || ''; + }; + + const increment = type => { + state[type]++; + stack.push(type); + }; + + const decrement = type => { + state[type]--; + stack.pop(); + }; + + /** + * Push tokens onto the tokens array. This helper speeds up + * tokenizing by 1) helping us avoid backtracking as much as possible, + * and 2) helping us avoid creating extra tokens when consecutive + * characters are plain text. This improves performance and simplifies + * lookbehinds. + */ + + const push = tok => { + if (prev.type === 'globstar') { + let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); + let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); + if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { + state.output = state.output.slice(0, -prev.output.length); + prev.type = 'star'; + prev.value = '*'; + prev.output = star; + state.output += prev.output; + } + } + + if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { + extglobs[extglobs.length - 1].inner += tok.value; + } + + if (tok.value || tok.output) append(tok); + if (prev && prev.type === 'text' && tok.type === 'text') { + prev.value += tok.value; + return; + } + + tok.prev = prev; + tokens.push(tok); + prev = tok; + }; + + const extglobOpen = (type, value) => { + let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; + + token.prev = prev; + token.parens = state.parens; + token.output = state.output; + let output = (opts.capture ? '(' : '') + token.open; + + push({ type, value, output: state.output ? '' : ONE_CHAR }); + push({ type: 'paren', extglob: true, value: advance(), output }); + increment('parens'); + extglobs.push(token); + }; + + const extglobClose = token => { + let output = token.close + (opts.capture ? ')' : ''); + + if (token.type === 'negate') { + let extglobStar = star; + + if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { + extglobStar = globstar(opts); + } + + if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { + output = token.close = ')$))' + extglobStar; + } + + if (token.prev.type === 'bos' && eos()) { + state.negatedExtglob = true; + } + } + + push({ type: 'paren', extglob: true, value, output }); + decrement('parens'); + }; + + if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { + let backslashes = false; + + let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { + if (first === '\\') { + backslashes = true; + return m; + } + + if (first === '?') { + if (esc) { + return esc + first + (rest ? QMARK.repeat(rest.length) : ''); + } + if (index === 0) { + return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); + } + return QMARK.repeat(chars.length); + } + + if (first === '.') { + return DOT_LITERAL.repeat(chars.length); + } + + if (first === '*') { + if (esc) { + return esc + first + (rest ? star : ''); + } + return star; + } + return esc ? m : '\\' + m; + }); + + if (backslashes === true) { + if (opts.unescape === true) { + output = output.replace(/\\/g, ''); + } else { + output = output.replace(/\\+/g, m => { + return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); + }); + } + } + + state.output = output; + return state; + } + + /** + * Tokenize input until we reach end-of-string + */ + + while (!eos()) { + value = advance(); + + if (value === '\u0000') { + continue; + } + + /** + * Escaped characters + */ + + if (value === '\\') { + let next = peek(); + + if (next === '/' && opts.bash !== true) { + continue; + } + + if (next === '.' || next === ';') { + continue; + } + + if (!next) { + value += '\\'; + push({ type: 'text', value }); + continue; + } + + // collapse slashes to reduce potential for exploits + let match = /^\\+/.exec(input.slice(state.index + 1)); + let slashes = 0; + + if (match && match[0].length > 2) { + slashes = match[0].length; + state.index += slashes; + if (slashes % 2 !== 0) { + value += '\\'; + } + } + + if (opts.unescape === true) { + value = advance() || ''; + } else { + value += advance() || ''; + } + + if (state.brackets === 0) { + push({ type: 'text', value }); + continue; + } + } + + /** + * If we're inside a regex character class, continue + * until we reach the closing bracket. + */ + + if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { + if (opts.posix !== false && value === ':') { + let inner = prev.value.slice(1); + if (inner.includes('[')) { + prev.posix = true; + + if (inner.includes(':')) { + let idx = prev.value.lastIndexOf('['); + let pre = prev.value.slice(0, idx); + let rest = prev.value.slice(idx + 2); + let posix = POSIX_REGEX_SOURCE[rest]; + if (posix) { + prev.value = pre + posix; + state.backtrack = true; + advance(); + + if (!bos.output && tokens.indexOf(prev) === 1) { + bos.output = ONE_CHAR; + } + continue; + } + } + } + } + + if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { + value = '\\' + value; + } + + if (value === ']' && (prev.value === '[' || prev.value === '[^')) { + value = '\\' + value; + } + + if (opts.posix === true && value === '!' && prev.value === '[') { + value = '^'; + } + + prev.value += value; + append({ value }); + continue; + } + + /** + * If we're inside a quoted string, continue + * until we reach the closing double quote. + */ + + if (state.quotes === 1 && value !== '"') { + value = utils.escapeRegex(value); + prev.value += value; + append({ value }); + continue; + } + + /** + * Double quotes + */ + + if (value === '"') { + state.quotes = state.quotes === 1 ? 0 : 1; + if (opts.keepQuotes === true) { + push({ type: 'text', value }); + } + continue; + } + + /** + * Parentheses + */ + + if (value === '(') { + push({ type: 'paren', value }); + increment('parens'); + continue; + } + + if (value === ')') { + if (state.parens === 0 && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '(')); + } + + let extglob = extglobs[extglobs.length - 1]; + if (extglob && state.parens === extglob.parens + 1) { + extglobClose(extglobs.pop()); + continue; + } + + push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); + decrement('parens'); + continue; + } + + /** + * Brackets + */ + + if (value === '[') { + if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { + if (opts.nobracket !== true && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('closing', ']')); + } + + value = '\\' + value; + } else { + increment('brackets'); + } + + push({ type: 'bracket', value }); + continue; + } + + if (value === ']') { + if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + if (state.brackets === 0) { + if (opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '[')); + } + + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + decrement('brackets'); + + let prevValue = prev.value.slice(1); + if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { + value = '/' + value; + } + + prev.value += value; + append({ value }); + + // when literal brackets are explicitly disabled + // assume we should match with a regex character class + if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { + continue; + } + + let escaped = utils.escapeRegex(prev.value); + state.output = state.output.slice(0, -prev.value.length); + + // when literal brackets are explicitly enabled + // assume we should escape the brackets to match literal characters + if (opts.literalBrackets === true) { + state.output += escaped; + prev.value = escaped; + continue; + } + + // when the user specifies nothing, try to match both + prev.value = `(${capture}${escaped}|${prev.value})`; + state.output += prev.value; + continue; + } + + /** + * Braces + */ + + if (value === '{' && opts.nobrace !== true) { + push({ type: 'brace', value, output: '(' }); + increment('braces'); + continue; + } + + if (value === '}') { + if (opts.nobrace === true || state.braces === 0) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + let output = ')'; + + if (state.dots === true) { + let arr = tokens.slice(); + let range = []; + + for (let i = arr.length - 1; i >= 0; i--) { + tokens.pop(); + if (arr[i].type === 'brace') { + break; + } + if (arr[i].type !== 'dots') { + range.unshift(arr[i].value); + } + } + + output = expandRange(range, opts); + state.backtrack = true; + } + + push({ type: 'brace', value, output }); + decrement('braces'); + continue; + } + + /** + * Pipes + */ + + if (value === '|') { + if (extglobs.length > 0) { + extglobs[extglobs.length - 1].conditions++; + } + push({ type: 'text', value }); + continue; + } + + /** + * Commas + */ + + if (value === ',') { + let output = value; + + if (state.braces > 0 && stack[stack.length - 1] === 'braces') { + output = '|'; + } + + push({ type: 'comma', value, output }); + continue; + } + + /** + * Slashes + */ + + if (value === '/') { + // if the beginning of the glob is "./", advance the start + // to the current index, and don't add the "./" characters + // to the state. This greatly simplifies lookbehinds when + // checking for BOS characters like "!" and "." (not "./") + if (prev.type === 'dot' && state.index === 1) { + state.start = state.index + 1; + state.consumed = ''; + state.output = ''; + tokens.pop(); + prev = bos; // reset "prev" to the first token + continue; + } + + push({ type: 'slash', value, output: SLASH_LITERAL }); + continue; + } + + /** + * Dots + */ + + if (value === '.') { + if (state.braces > 0 && prev.type === 'dot') { + if (prev.value === '.') prev.output = DOT_LITERAL; + prev.type = 'dots'; + prev.output += value; + prev.value += value; + state.dots = true; + continue; + } + + push({ type: 'dot', value, output: DOT_LITERAL }); + continue; + } + + /** + * Question marks + */ + + if (value === '?') { + if (prev && prev.type === 'paren') { + let next = peek(); + let output = value; + + if (next === '<' && !utils.supportsLookbehinds()) { + throw new Error('Node.js v10 or higher is required for regex lookbehinds'); + } + + if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { + output = '\\' + value; + } + + push({ type: 'text', value, output }); + continue; + } + + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('qmark', value); + continue; + } + + if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { + push({ type: 'qmark', value, output: QMARK_NO_DOT }); + continue; + } + + push({ type: 'qmark', value, output: QMARK }); + continue; + } + + /** + * Exclamation + */ + + if (value === '!') { + if (opts.noextglob !== true && peek() === '(') { + if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { + extglobOpen('negate', value); + continue; + } + } + + if (opts.nonegate !== true && state.index === 0) { + negate(state); + continue; + } + } + + /** + * Plus + */ + + if (value === '+') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('plus', value); + continue; + } + + if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { + let output = prev.extglob === true ? '\\' + value : value; + push({ type: 'plus', value, output }); + continue; + } + + // use regex behavior inside parens + if (state.parens > 0 && opts.regex !== false) { + push({ type: 'plus', value }); + continue; + } + + push({ type: 'plus', value: PLUS_LITERAL }); + continue; + } + + /** + * Plain text + */ + + if (value === '@') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + push({ type: 'at', value, output: '' }); + continue; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Plain text + */ + + if (value !== '*') { + if (value === '$' || value === '^') { + value = '\\' + value; + } + + let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); + if (match) { + value += match[0]; + state.index += match[0].length; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Stars + */ + + if (prev && (prev.type === 'globstar' || prev.star === true)) { + prev.type = 'star'; + prev.star = true; + prev.value += value; + prev.output = star; + state.backtrack = true; + state.consumed += value; + continue; + } + + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('star', value); + continue; + } + + if (prev.type === 'star') { + if (opts.noglobstar === true) { + state.consumed += value; + continue; + } + + let prior = prev.prev; + let before = prior.prev; + let isStart = prior.type === 'slash' || prior.type === 'bos'; + let afterStar = before && (before.type === 'star' || before.type === 'globstar'); + + if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { + push({ type: 'star', value, output: '' }); + continue; + } + + let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); + let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); + if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { + push({ type: 'star', value, output: '' }); + continue; + } + + // strip consecutive `/**/` + while (input.slice(state.index + 1, state.index + 4) === '/**') { + let after = input[state.index + 4]; + if (after && after !== '/') { + break; + } + state.consumed += '/**'; + state.index += 3; + } + + if (prior.type === 'bos' && eos()) { + prev.type = 'globstar'; + prev.value += value; + prev.output = globstar(opts); + state.output = prev.output; + state.consumed += value; + continue; + } + + if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + + prev.type = 'globstar'; + prev.output = globstar(opts) + '|$)'; + prev.value += value; + + state.output += prior.output + prev.output; + state.consumed += value; + continue; + } + + let next = peek(); + if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { + let end = peek(2) !== void 0 ? '|$' : ''; + + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + + prev.type = 'globstar'; + prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; + prev.value += value; + + state.output += prior.output + prev.output; + state.consumed += value + advance(); + + push({ type: 'slash', value, output: '' }); + continue; + } + + if (prior.type === 'bos' && next === '/') { + prev.type = 'globstar'; + prev.value += value; + prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; + state.output = prev.output; + state.consumed += value + advance(); + push({ type: 'slash', value, output: '' }); + continue; + } + + // remove single star from output + state.output = state.output.slice(0, -prev.output.length); + + // reset previous token to globstar + prev.type = 'globstar'; + prev.output = globstar(opts); + prev.value += value; + + // reset output with globstar + state.output += prev.output; + state.consumed += value; + continue; + } + + let token = { type: 'star', value, output: star }; + + if (opts.bash === true) { + token.output = '.*?'; + if (prev.type === 'bos' || prev.type === 'slash') { + token.output = nodot + token.output; + } + push(token); + continue; + } + + if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { + token.output = value; + push(token); + continue; + } + + if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { + if (prev.type === 'dot') { + state.output += NO_DOT_SLASH; + prev.output += NO_DOT_SLASH; + + } else if (opts.dot === true) { + state.output += NO_DOTS_SLASH; + prev.output += NO_DOTS_SLASH; + + } else { + state.output += nodot; + prev.output += nodot; + } + + if (peek() !== '*') { + state.output += ONE_CHAR; + prev.output += ONE_CHAR; + } + } + + push(token); + } + + while (state.brackets > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); + state.output = utils.escapeLast(state.output, '['); + decrement('brackets'); + } + + while (state.parens > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); + state.output = utils.escapeLast(state.output, '('); + decrement('parens'); + } + + while (state.braces > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); + state.output = utils.escapeLast(state.output, '{'); + decrement('braces'); + } + + if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { + push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); + } + + // rebuild the output if we had to backtrack at any point + if (state.backtrack === true) { + state.output = ''; + + for (let token of state.tokens) { + state.output += token.output != null ? token.output : token.value; + + if (token.suffix) { + state.output += token.suffix; + } + } + } + + return state; +}; + +/** + * Fast paths for creating regular expressions for common glob patterns. + * This can significantly speed up processing and has very little downside + * impact when none of the fast paths match. + */ + +parse.fastpaths = (input, options) => { + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + + input = REPLACEMENTS[input] || input; + let win32 = utils.isWindows(options); + + // create constants based on platform, for windows or posix + const { + DOT_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOTS_SLASH, + STAR, + START_ANCHOR + } = constants.globChars(win32); + + let capture = opts.capture ? '' : '?:'; + let star = opts.bash === true ? '.*?' : STAR; + let nodot = opts.dot ? NO_DOTS : NO_DOT; + let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + + if (opts.capture) { + star = `(${star})`; + } + + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + const create = str => { + switch (str) { + case '*': + return `${nodot}${ONE_CHAR}${star}`; + + case '.*': + return `${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '*.*': + return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '*/*': + return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + + case '**': + return nodot + globstar(opts); + + case '**/*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + + case '**/*.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '**/.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; + + default: { + let match = /^(.*?)\.(\w+)$/.exec(str); + if (!match) return; + + let source = create(match[1], options); + if (!source) return; + + return source + DOT_LITERAL + match[2]; + } + } + }; + + let output = create(input); + if (output && opts.strictSlashes !== true) { + output += `${SLASH_LITERAL}?`; + } + + return output; +}; + +module.exports = parse; + + +/***/ }), +/* 206 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const merge2 = __webpack_require__(177); +function merge(streams) { + const mergedStream = merge2(streams); + streams.forEach((stream) => { + stream.once('error', (err) => mergedStream.emit('error', err)); + }); + return mergedStream; +} +exports.merge = merge; + + +/***/ }), +/* 207 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(208); +const provider_1 = __webpack_require__(235); +class ProviderAsync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = []; + return new Promise((resolve, reject) => { + const stream = this.api(root, task, options); + stream.once('error', reject); + stream.on('data', (entry) => entries.push(options.transform(entry))); + stream.once('end', () => resolve(entries)); + }); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderAsync; + + +/***/ }), +/* 208 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(28); +const fsStat = __webpack_require__(209); +const fsWalk = __webpack_require__(214); +const reader_1 = __webpack_require__(234); +class ReaderStream extends reader_1.default { + constructor() { + super(...arguments); + this._walkStream = fsWalk.walkStream; + this._stat = fsStat.stat; + } + dynamic(root, options) { + return this._walkStream(root, options); + } + static(patterns, options) { + const filepaths = patterns.map(this._getFullEntryPath, this); + const stream = new stream_1.PassThrough({ objectMode: true }); + stream._write = (index, _enc, done) => { + return this._getEntry(filepaths[index], patterns[index], options) + .then((entry) => { + if (entry !== null && options.entryFilter(entry)) { + stream.push(entry); + } + if (index === filepaths.length - 1) { + stream.end(); + } + done(); + }) + .catch(done); + }; + for (let i = 0; i < filepaths.length; i++) { + stream.write(i); + } + return stream; + } + _getEntry(filepath, pattern, options) { + return this._getStat(filepath) + .then((stats) => this._makeEntry(stats, pattern)) + .catch((error) => { + if (options.errorFilter(error)) { + return null; + } + throw error; + }); + } + _getStat(filepath) { + return new Promise((resolve, reject) => { + this._stat(filepath, this._fsStatSettings, (error, stats) => { + error ? reject(error) : resolve(stats); + }); + }); + } +} +exports.default = ReaderStream; + + +/***/ }), +/* 209 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(210); +const sync = __webpack_require__(211); +const settings_1 = __webpack_require__(212); +exports.Settings = settings_1.default; +function stat(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.stat = stat; +function statSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.statSync = statSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 210 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings, callback) { + settings.fs.lstat(path, (lstatError, lstat) => { + if (lstatError) { + return callFailureCallback(callback, lstatError); + } + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return callSuccessCallback(callback, lstat); + } + settings.fs.stat(path, (statError, stat) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return callFailureCallback(callback, statError); + } + return callSuccessCallback(callback, lstat); + } + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + callSuccessCallback(callback, stat); + }); + }); +} +exports.read = read; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} + + +/***/ }), +/* 211 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings) { + const lstat = settings.fs.lstatSync(path); + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return lstat; + } + try { + const stat = settings.fs.statSync(path); + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + return stat; + } + catch (error) { + if (!settings.throwErrorOnBrokenSymbolicLink) { + return lstat; + } + throw error; + } +} +exports.read = read; + + +/***/ }), +/* 212 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(213); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLink = this._getValue(this._options.followSymbolicLink, true); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.markSymbolicLink = this._getValue(this._options.markSymbolicLink, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 213 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; + + +/***/ }), +/* 214 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(215); +const stream_1 = __webpack_require__(230); +const sync_1 = __webpack_require__(231); +const settings_1 = __webpack_require__(233); +exports.Settings = settings_1.default; +function walk(dir, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return new async_1.default(dir, getSettings()).read(optionsOrSettingsOrCallback); + } + new async_1.default(dir, getSettings(optionsOrSettingsOrCallback)).read(callback); +} +exports.walk = walk; +function walkSync(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new sync_1.default(dir, settings); + return provider.read(); +} +exports.walkSync = walkSync; +function walkStream(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new stream_1.default(dir, settings); + return provider.read(); +} +exports.walkStream = walkStream; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 215 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(216); +class AsyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._storage = new Set(); + } + read(callback) { + this._reader.onError((error) => { + callFailureCallback(callback, error); + }); + this._reader.onEntry((entry) => { + this._storage.add(entry); + }); + this._reader.onEnd(() => { + callSuccessCallback(callback, Array.from(this._storage)); + }); + this._reader.read(); + } +} +exports.default = AsyncProvider; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, entries) { + callback(null, entries); +} + + +/***/ }), +/* 216 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = __webpack_require__(46); +const fsScandir = __webpack_require__(217); +const fastq = __webpack_require__(226); +const common = __webpack_require__(228); +const reader_1 = __webpack_require__(229); +class AsyncReader extends reader_1.default { + constructor(_root, _settings) { + super(_root, _settings); + this._settings = _settings; + this._scandir = fsScandir.scandir; + this._emitter = new events_1.EventEmitter(); + this._queue = fastq(this._worker.bind(this), this._settings.concurrency); + this._isFatalError = false; + this._isDestroyed = false; + this._queue.drain = () => { + if (!this._isFatalError) { + this._emitter.emit('end'); + } + }; + } + read() { + this._isFatalError = false; + this._isDestroyed = false; + setImmediate(() => { + this._pushToQueue(this._root, this._settings.basePath); + }); + return this._emitter; + } + destroy() { + if (this._isDestroyed) { + throw new Error('The reader is already destroyed'); + } + this._isDestroyed = true; + this._queue.killAndDrain(); + } + onEntry(callback) { + this._emitter.on('entry', callback); + } + onError(callback) { + this._emitter.once('error', callback); + } + onEnd(callback) { + this._emitter.once('end', callback); + } + _pushToQueue(dir, base) { + const queueItem = { dir, base }; + this._queue.push(queueItem, (error) => { + if (error) { + this._handleError(error); + } + }); + } + _worker(item, done) { + this._scandir(item.dir, this._settings.fsScandirSettings, (error, entries) => { + if (error) { + return done(error, undefined); + } + for (const entry of entries) { + this._handleEntry(entry, item.base); + } + done(null, undefined); + }); + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + this._isFatalError = true; + this._isDestroyed = true; + this._emitter.emit('error', error); + } + _handleEntry(entry, base) { + if (this._isDestroyed || this._isFatalError) { + return; + } + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._emitEntry(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _emitEntry(entry) { + this._emitter.emit('entry', entry); + } +} +exports.default = AsyncReader; + + +/***/ }), +/* 217 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(218); +const sync = __webpack_require__(223); +const settings_1 = __webpack_require__(224); +exports.Settings = settings_1.default; +function scandir(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.scandir = scandir; +function scandirSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.scandirSync = scandirSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 218 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(209); +const rpl = __webpack_require__(219); +const constants_1 = __webpack_require__(220); +const utils = __webpack_require__(221); +function read(dir, settings, callback) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings, callback); + } + return readdir(dir, settings, callback); +} +exports.read = read; +function readdirWithFileTypes(dir, settings, callback) { + settings.fs.readdir(dir, { withFileTypes: true }, (readdirError, dirents) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const entries = dirents.map((dirent) => ({ + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + })); + if (!settings.followSymbolicLinks) { + return callSuccessCallback(callback, entries); + } + const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings)); + rpl(tasks, (rplError, rplEntries) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + callSuccessCallback(callback, rplEntries); + }); + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function makeRplTaskEntry(entry, settings) { + return (done) => { + if (!entry.dirent.isSymbolicLink()) { + return done(null, entry); + } + settings.fs.stat(entry.path, (statError, stats) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return done(statError); + } + return done(null, entry); + } + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + return done(null, entry); + }); + }; +} +function readdir(dir, settings, callback) { + settings.fs.readdir(dir, (readdirError, names) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const filepaths = names.map((name) => `${dir}${settings.pathSegmentSeparator}${name}`); + const tasks = filepaths.map((filepath) => { + return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); + }); + rpl(tasks, (rplError, results) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + const entries = []; + for (let index = 0; index < names.length; index++) { + const name = names[index]; + const stats = results[index]; + const entry = { + name, + path: filepaths[index], + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + entries.push(entry); + } + callSuccessCallback(callback, entries); + }); + }); +} +exports.readdir = readdir; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} + + +/***/ }), +/* 219 */ +/***/ (function(module, exports) { + +module.exports = runParallel + +function runParallel (tasks, cb) { + var results, pending, keys + var isSync = true + + if (Array.isArray(tasks)) { + results = [] + pending = tasks.length + } else { + keys = Object.keys(tasks) + results = {} + pending = keys.length + } + + function done (err) { + function end () { + if (cb) cb(err, results) + cb = null + } + if (isSync) process.nextTick(end) + else end() + } + + function each (i, err, result) { + results[i] = result + if (--pending === 0 || err) { + done(err) + } + } + + if (!pending) { + // empty + done(null) + } else if (keys) { + // object + keys.forEach(function (key) { + tasks[key](function (err, result) { each(key, err, result) }) + }) + } else { + // array + tasks.forEach(function (task, i) { + task(function (err, result) { each(i, err, result) }) + }) + } + + isSync = false +} + + +/***/ }), +/* 220 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); +const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); +const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); +/** + * IS `true` for Node.js 10.10 and greater. + */ +exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = MAJOR_VERSION > 10 || (MAJOR_VERSION === 10 && MINOR_VERSION >= 10); + + +/***/ }), +/* 221 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(222); +exports.fs = fs; + + +/***/ }), +/* 222 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; + + +/***/ }), +/* 223 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(209); +const constants_1 = __webpack_require__(220); +const utils = __webpack_require__(221); +function read(dir, settings) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings); + } + return readdir(dir, settings); +} +exports.read = read; +function readdirWithFileTypes(dir, settings) { + const dirents = settings.fs.readdirSync(dir, { withFileTypes: true }); + return dirents.map((dirent) => { + const entry = { + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + }; + if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { + try { + const stats = settings.fs.statSync(entry.path); + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + } + catch (error) { + if (settings.throwErrorOnBrokenSymbolicLink) { + throw error; + } + } + } + return entry; + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function readdir(dir, settings) { + const names = settings.fs.readdirSync(dir); + return names.map((name) => { + const entryPath = `${dir}${settings.pathSegmentSeparator}${name}`; + const stats = fsStat.statSync(entryPath, settings.fsStatSettings); + const entry = { + name, + path: entryPath, + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + return entry; + }); +} +exports.readdir = readdir; + + +/***/ }), +/* 224 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(209); +const fs = __webpack_require__(225); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, false); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.stats = this._getValue(this._options.stats, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + this.fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this.followSymbolicLinks, + fs: this.fs, + throwErrorOnBrokenSymbolicLink: this.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 225 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; + + +/***/ }), +/* 226 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var reusify = __webpack_require__(227) + +function fastqueue (context, worker, concurrency) { + if (typeof context === 'function') { + concurrency = worker + worker = context + context = null + } + + var cache = reusify(Task) + var queueHead = null + var queueTail = null + var _running = 0 + + var self = { + push: push, + drain: noop, + saturated: noop, + pause: pause, + paused: false, + concurrency: concurrency, + running: running, + resume: resume, + idle: idle, + length: length, + unshift: unshift, + empty: noop, + kill: kill, + killAndDrain: killAndDrain + } + + return self + + function running () { + return _running + } + + function pause () { + self.paused = true + } + + function length () { + var current = queueHead + var counter = 0 + + while (current) { + current = current.next + counter++ + } + + return counter + } + + function resume () { + if (!self.paused) return + self.paused = false + for (var i = 0; i < self.concurrency; i++) { + _running++ + release() + } + } + + function idle () { + return _running === 0 && self.length() === 0 + } + + function push (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + + if (_running === self.concurrency || self.paused) { + if (queueTail) { + queueTail.next = current + queueTail = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function unshift (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + + if (_running === self.concurrency || self.paused) { + if (queueHead) { + current.next = queueHead + queueHead = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function release (holder) { + if (holder) { + cache.release(holder) + } + var next = queueHead + if (next) { + if (!self.paused) { + if (queueTail === queueHead) { + queueTail = null + } + queueHead = next.next + next.next = null + worker.call(context, next.value, next.worked) + if (queueTail === null) { + self.empty() + } + } else { + _running-- + } + } else if (--_running === 0) { + self.drain() + } + } + + function kill () { + queueHead = null + queueTail = null + self.drain = noop + } + + function killAndDrain () { + queueHead = null + queueTail = null + self.drain() + self.drain = noop + } +} + +function noop () {} + +function Task () { + this.value = null + this.callback = noop + this.next = null + this.release = noop + this.context = null + + var self = this + + this.worked = function worked (err, result) { + var callback = self.callback + self.value = null + self.callback = noop + callback.call(self.context, err, result) + self.release(self) + } +} + +module.exports = fastqueue + + +/***/ }), +/* 227 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function reusify (Constructor) { + var head = new Constructor() + var tail = head + + function get () { + var current = head + + if (current.next) { + head = current.next + } else { + head = new Constructor() + tail = head + } + + current.next = null + + return current + } + + function release (obj) { + tail.next = obj + tail = obj + } + + return { + get: get, + release: release + } +} + +module.exports = reusify + + +/***/ }), +/* 228 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isFatalError(settings, error) { + if (settings.errorFilter === null) { + return true; + } + return !settings.errorFilter(error); +} +exports.isFatalError = isFatalError; +function isAppliedFilter(filter, value) { + return filter === null || filter(value); +} +exports.isAppliedFilter = isAppliedFilter; +function replacePathSegmentSeparator(filepath, separator) { + return filepath.split(/[\\\/]/).join(separator); +} +exports.replacePathSegmentSeparator = replacePathSegmentSeparator; +function joinPathSegments(a, b, separator) { + if (a === '') { + return b; + } + return a + separator + b; +} +exports.joinPathSegments = joinPathSegments; + + +/***/ }), +/* 229 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const common = __webpack_require__(228); +class Reader { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._root = common.replacePathSegmentSeparator(_root, _settings.pathSegmentSeparator); + } +} +exports.default = Reader; + + +/***/ }), +/* 230 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(28); +const async_1 = __webpack_require__(216); +class StreamProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._stream = new stream_1.Readable({ + objectMode: true, + read: () => { }, + destroy: this._reader.destroy.bind(this._reader) + }); + } + read() { + this._reader.onError((error) => { + this._stream.emit('error', error); + }); + this._reader.onEntry((entry) => { + this._stream.push(entry); + }); + this._reader.onEnd(() => { + this._stream.push(null); + }); + this._reader.read(); + return this._stream; + } +} +exports.default = StreamProvider; + + +/***/ }), +/* 231 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(232); +class SyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new sync_1.default(this._root, this._settings); + } + read() { + return this._reader.read(); + } +} +exports.default = SyncProvider; + + +/***/ }), +/* 232 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsScandir = __webpack_require__(217); +const common = __webpack_require__(228); +const reader_1 = __webpack_require__(229); +class SyncReader extends reader_1.default { + constructor() { + super(...arguments); + this._scandir = fsScandir.scandirSync; + this._storage = new Set(); + this._queue = new Set(); + } + read() { + this._pushToQueue(this._root, this._settings.basePath); + this._handleQueue(); + return Array.from(this._storage); + } + _pushToQueue(dir, base) { + this._queue.add({ dir, base }); + } + _handleQueue() { + for (const item of this._queue.values()) { + this._handleDirectory(item.dir, item.base); + } + } + _handleDirectory(dir, base) { + try { + const entries = this._scandir(dir, this._settings.fsScandirSettings); + for (const entry of entries) { + this._handleEntry(entry, base); + } + } + catch (error) { + this._handleError(error); + } + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + throw error; + } + _handleEntry(entry, base) { + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._pushToStorage(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _pushToStorage(entry) { + this._storage.add(entry); + } +} +exports.default = SyncReader; + + +/***/ }), +/* 233 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsScandir = __webpack_require__(217); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.basePath = this._getValue(this._options.basePath, undefined); + this.concurrency = this._getValue(this._options.concurrency, Infinity); + this.deepFilter = this._getValue(this._options.deepFilter, null); + this.entryFilter = this._getValue(this._options.entryFilter, null); + this.errorFilter = this._getValue(this._options.errorFilter, null); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.fsScandirSettings = new fsScandir.Settings({ + followSymbolicLinks: this._options.followSymbolicLinks, + fs: this._options.fs, + pathSegmentSeparator: this._options.pathSegmentSeparator, + stats: this._options.stats, + throwErrorOnBrokenSymbolicLink: this._options.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 234 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(209); +const utils = __webpack_require__(180); +class Reader { + constructor(_settings) { + this._settings = _settings; + this._fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this._settings.followSymbolicLinks, + fs: this._settings.fs, + throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks + }); + } + _getFullEntryPath(filepath) { + return path.resolve(this._settings.cwd, filepath); + } + _makeEntry(stats, pattern) { + const entry = { + name: pattern, + path: pattern, + dirent: utils.fs.createDirentFromStats(pattern, stats) + }; + if (this._settings.stats) { + entry.stats = stats; + } + return entry; + } + _isFatalError(error) { + return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; + } +} +exports.default = Reader; + + +/***/ }), +/* 235 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const deep_1 = __webpack_require__(236); +const entry_1 = __webpack_require__(237); +const error_1 = __webpack_require__(238); +const entry_2 = __webpack_require__(239); +class Provider { + constructor(_settings) { + this._settings = _settings; + this.errorFilter = new error_1.default(this._settings); + this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions()); + this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions()); + this.entryTransformer = new entry_2.default(this._settings); + } + _getRootDirectory(task) { + return path.resolve(this._settings.cwd, task.base); + } + _getReaderOptions(task) { + const basePath = task.base === '.' ? '' : task.base; + return { + basePath, + pathSegmentSeparator: '/', + concurrency: this._settings.concurrency, + deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), + entryFilter: this.entryFilter.getFilter(task.positive, task.negative), + errorFilter: this.errorFilter.getFilter(), + followSymbolicLinks: this._settings.followSymbolicLinks, + fs: this._settings.fs, + stats: this._settings.stats, + throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, + transform: this.entryTransformer.getTransformer() + }; + } + _getMicromatchOptions() { + return { + dot: this._settings.dot, + matchBase: this._settings.baseNameMatch, + nobrace: !this._settings.braceExpansion, + nocase: !this._settings.caseSensitiveMatch, + noext: !this._settings.extglob, + noglobstar: !this._settings.globstar, + posix: true, + strictSlashes: false + }; + } +} +exports.default = Provider; + + +/***/ }), +/* 236 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class DeepFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + } + getFilter(basePath, positive, negative) { + const maxPatternDepth = this._getMaxPatternDepth(positive); + const negativeRe = this._getNegativePatternsRe(negative); + return (entry) => this._filter(basePath, entry, negativeRe, maxPatternDepth); + } + _getMaxPatternDepth(patterns) { + const globstar = patterns.some(utils.pattern.hasGlobStar); + return globstar ? Infinity : utils.pattern.getMaxNaivePatternsDepth(patterns); + } + _getNegativePatternsRe(patterns) { + const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); + return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); + } + _filter(basePath, entry, negativeRe, maxPatternDepth) { + const depth = this._getEntryDepth(basePath, entry.path); + if (this._isSkippedByDeep(depth)) { + return false; + } + if (this._isSkippedByMaxPatternDepth(depth, maxPatternDepth)) { + return false; + } + if (this._isSkippedSymbolicLink(entry)) { + return false; + } + if (this._isSkippedDotDirectory(entry)) { + return false; + } + return this._isSkippedByNegativePatterns(entry, negativeRe); + } + _getEntryDepth(basePath, entryPath) { + const basePathDepth = basePath.split('/').length; + const entryPathDepth = entryPath.split('/').length; + return entryPathDepth - (basePath === '' ? 0 : basePathDepth); + } + _isSkippedByDeep(entryDepth) { + return entryDepth >= this._settings.deep; + } + _isSkippedByMaxPatternDepth(entryDepth, maxPatternDepth) { + return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; + } + _isSkippedSymbolicLink(entry) { + return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); + } + _isSkippedDotDirectory(entry) { + return !this._settings.dot && entry.name.startsWith('.'); + } + _isSkippedByNegativePatterns(entry, negativeRe) { + return !utils.pattern.matchAny(entry.path, negativeRe); + } +} +exports.default = DeepFilter; + + +/***/ }), +/* 237 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class EntryFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + this.index = new Map(); + } + getFilter(positive, negative) { + const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); + const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); + return (entry) => this._filter(entry, positiveRe, negativeRe); + } + _filter(entry, positiveRe, negativeRe) { + if (this._settings.unique) { + if (this._isDuplicateEntry(entry)) { + return false; + } + this._createIndexRecord(entry); + } + if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { + return false; + } + if (this._isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { + return false; + } + const filepath = this._settings.baseNameMatch ? entry.name : entry.path; + return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); + } + _isDuplicateEntry(entry) { + return this.index.has(entry.path); + } + _createIndexRecord(entry) { + this.index.set(entry.path, undefined); + } + _onlyFileFilter(entry) { + return this._settings.onlyFiles && !entry.dirent.isFile(); + } + _onlyDirectoryFilter(entry) { + return this._settings.onlyDirectories && !entry.dirent.isDirectory(); + } + _isSkippedByAbsoluteNegativePatterns(entry, negativeRe) { + if (!this._settings.absolute) { + return false; + } + const fullpath = utils.path.makeAbsolute(this._settings.cwd, entry.path); + return this._isMatchToPatterns(fullpath, negativeRe); + } + _isMatchToPatterns(filepath, patternsRe) { + return utils.pattern.matchAny(filepath, patternsRe); + } +} +exports.default = EntryFilter; + + +/***/ }), +/* 238 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class ErrorFilter { + constructor(_settings) { + this._settings = _settings; + } + getFilter() { + return (error) => this._isNonFatalError(error); + } + _isNonFatalError(error) { + return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; + } +} +exports.default = ErrorFilter; + + +/***/ }), +/* 239 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class EntryTransformer { + constructor(_settings) { + this._settings = _settings; + } + getTransformer() { + return (entry) => this._transform(entry); + } + _transform(entry) { + let filepath = entry.path; + if (this._settings.absolute) { + filepath = utils.path.makeAbsolute(this._settings.cwd, filepath); + filepath = utils.path.unixify(filepath); + } + if (this._settings.markDirectories && entry.dirent.isDirectory()) { + filepath += '/'; + } + if (!this._settings.objectMode) { + return filepath; + } + return Object.assign({}, entry, { path: filepath }); + } +} +exports.default = EntryTransformer; + + +/***/ }), +/* 240 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(28); +const stream_2 = __webpack_require__(208); +const provider_1 = __webpack_require__(235); +class ProviderStream extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_2.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const source = this.api(root, task, options); + const dest = new stream_1.Readable({ objectMode: true, read: () => { } }); + source + .once('error', (error) => dest.emit('error', error)) + .on('data', (entry) => dest.emit('data', options.transform(entry))) + .once('end', () => dest.emit('end')); + return dest; + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderStream; + + +/***/ }), +/* 241 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(242); +const provider_1 = __webpack_require__(235); +class ProviderSync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new sync_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = this.api(root, task, options); + return entries.map(options.transform); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderSync; + + +/***/ }), +/* 242 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(209); +const fsWalk = __webpack_require__(214); +const reader_1 = __webpack_require__(234); +class ReaderSync extends reader_1.default { + constructor() { + super(...arguments); + this._walkSync = fsWalk.walkSync; + this._statSync = fsStat.statSync; + } + dynamic(root, options) { + return this._walkSync(root, options); + } + static(patterns, options) { + const entries = []; + for (const pattern of patterns) { + const filepath = this._getFullEntryPath(pattern); + const entry = this._getEntry(filepath, pattern, options); + if (entry === null || !options.entryFilter(entry)) { + continue; + } + entries.push(entry); + } + return entries; + } + _getEntry(filepath, pattern, options) { + try { + const stats = this._getStat(filepath); + return this._makeEntry(stats, pattern); + } + catch (error) { + if (options.errorFilter(error)) { + return null; + } + throw error; + } + } + _getStat(filepath) { + return this._statSync(filepath, this._fsStatSettings); + } +} +exports.default = ReaderSync; + + +/***/ }), +/* 243 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +const os = __webpack_require__(11); +const CPU_COUNT = os.cpus().length; +exports.DEFAULT_FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + lstatSync: fs.lstatSync, + stat: fs.stat, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +// tslint:enable no-redundant-jsdoc +class Settings { + constructor(_options = {}) { + this._options = _options; + this.absolute = this._getValue(this._options.absolute, false); + this.baseNameMatch = this._getValue(this._options.baseNameMatch, false); + this.braceExpansion = this._getValue(this._options.braceExpansion, true); + this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true); + this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT); + this.cwd = this._getValue(this._options.cwd, process.cwd()); + this.deep = this._getValue(this._options.deep, Infinity); + this.dot = this._getValue(this._options.dot, false); + this.extglob = this._getValue(this._options.extglob, true); + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true); + this.fs = this._getFileSystemMethods(this._options.fs); + this.globstar = this._getValue(this._options.globstar, true); + this.ignore = this._getValue(this._options.ignore, []); + this.markDirectories = this._getValue(this._options.markDirectories, false); + this.objectMode = this._getValue(this._options.objectMode, false); + this.onlyDirectories = this._getValue(this._options.onlyDirectories, false); + this.onlyFiles = this._getValue(this._options.onlyFiles, true); + this.stats = this._getValue(this._options.stats, false); + this.suppressErrors = this._getValue(this._options.suppressErrors, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); + this.unique = this._getValue(this._options.unique, true); + if (this.onlyDirectories) { + this.onlyFiles = false; + } + if (this.stats) { + this.objectMode = true; + } + } + _getValue(option, value) { + return option === undefined ? value : option; + } + _getFileSystemMethods(methods = {}) { + return Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER, methods); + } +} +exports.default = Settings; + + +/***/ }), +/* 244 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const path = __webpack_require__(16); +const pathType = __webpack_require__(245); + +const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; + +const getPath = (filepath, cwd) => { + const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; + return path.isAbsolute(pth) ? pth : path.join(cwd, pth); +}; + +const addExtensions = (file, extensions) => { + if (path.extname(file)) { + return `**/${file}`; + } + + return `**/${file}.${getExtensions(extensions)}`; +}; + +const getGlob = (directory, options) => { + if (options.files && !Array.isArray(options.files)) { + throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof options.files}\``); + } + + if (options.extensions && !Array.isArray(options.extensions)) { + throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof options.extensions}\``); + } + + if (options.files && options.extensions) { + return options.files.map(x => path.posix.join(directory, addExtensions(x, options.extensions))); + } + + if (options.files) { + return options.files.map(x => path.posix.join(directory, `**/${x}`)); + } + + if (options.extensions) { + return [path.posix.join(directory, `**/*.${getExtensions(options.extensions)}`)]; + } + + return [path.posix.join(directory, '**')]; +}; + +module.exports = async (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + + const globs = await Promise.all([].concat(input).map(async x => { + const isDirectory = await pathType.isDirectory(getPath(x, options.cwd)); + return isDirectory ? getGlob(x, options) : x; + })); + + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; + +module.exports.sync = (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + + const globs = [].concat(input).map(x => pathType.isDirectorySync(getPath(x, options.cwd)) ? getGlob(x, options) : x); + + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; + + +/***/ }), +/* 245 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); + +async function isType(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + + try { + const stats = await promisify(fs[fsStatType])(filePath); + return stats[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +function isTypeSync(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + + try { + return fs[fsStatType](filePath)[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +exports.isFile = isType.bind(null, 'stat', 'isFile'); +exports.isDirectory = isType.bind(null, 'stat', 'isDirectory'); +exports.isSymlink = isType.bind(null, 'lstat', 'isSymbolicLink'); +exports.isFileSync = isTypeSync.bind(null, 'statSync', 'isFile'); +exports.isDirectorySync = isTypeSync.bind(null, 'statSync', 'isDirectory'); +exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); + + +/***/ }), +/* 246 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); +const path = __webpack_require__(16); +const fastGlob = __webpack_require__(178); +const gitIgnore = __webpack_require__(247); +const slash = __webpack_require__(248); + +const DEFAULT_IGNORE = [ + '**/node_modules/**', + '**/flow-typed/**', + '**/coverage/**', + '**/.git' +]; + +const readFileP = promisify(fs.readFile); + +const mapGitIgnorePatternTo = base => ignore => { + if (ignore.startsWith('!')) { + return '!' + path.posix.join(base, ignore.slice(1)); + } + + return path.posix.join(base, ignore); +}; + +const parseGitIgnore = (content, options) => { + const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); + + return content + .split(/\r?\n/) + .filter(Boolean) + .filter(line => !line.startsWith('#')) + .map(mapGitIgnorePatternTo(base)); +}; + +const reduceIgnore = files => { + return files.reduce((ignores, file) => { + ignores.add(parseGitIgnore(file.content, { + cwd: file.cwd, + fileName: file.filePath + })); + return ignores; + }, gitIgnore()); +}; + +const ensureAbsolutePathForCwd = (cwd, p) => { + if (path.isAbsolute(p)) { + if (p.startsWith(cwd)) { + return p; + } + + throw new Error(`Path ${p} is not in cwd ${cwd}`); + } + + return path.join(cwd, p); +}; + +const getIsIgnoredPredecate = (ignores, cwd) => { + return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); +}; + +const getFile = async (file, cwd) => { + const filePath = path.join(cwd, file); + const content = await readFileP(filePath, 'utf8'); + + return { + cwd, + filePath, + content + }; +}; + +const getFileSync = (file, cwd) => { + const filePath = path.join(cwd, file); + const content = fs.readFileSync(filePath, 'utf8'); + + return { + cwd, + filePath, + content + }; +}; + +const normalizeOptions = ({ + ignore = [], + cwd = process.cwd() +} = {}) => { + return {ignore, cwd}; +}; + +module.exports = async options => { + options = normalizeOptions(options); + + const paths = await fastGlob('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + + const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); + const ignores = reduceIgnore(files); + + return getIsIgnoredPredecate(ignores, options.cwd); +}; + +module.exports.sync = options => { + options = normalizeOptions(options); + + const paths = fastGlob.sync('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + + const files = paths.map(file => getFileSync(file, options.cwd)); + const ignores = reduceIgnore(files); + + return getIsIgnoredPredecate(ignores, options.cwd); +}; + + +/***/ }), +/* 247 */ +/***/ (function(module, exports) { + +// A simple implementation of make-array +function makeArray (subject) { + return Array.isArray(subject) + ? subject + : [subject] +} + +const REGEX_TEST_BLANK_LINE = /^\s+$/ +const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/ +const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/ +const REGEX_SPLITALL_CRLF = /\r?\n/g +// /foo, +// ./foo, +// ../foo, +// . +// .. +const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/ + +const SLASH = '/' +const KEY_IGNORE = typeof Symbol !== 'undefined' + ? Symbol.for('node-ignore') + /* istanbul ignore next */ + : 'node-ignore' + +const define = (object, key, value) => + Object.defineProperty(object, key, {value}) + +const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g + +// Sanitize the range of a regular expression +// The cases are complicated, see test cases for details +const sanitizeRange = range => range.replace( + REGEX_REGEXP_RANGE, + (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) + ? match + // Invalid range (out of order) which is ok for gitignore rules but + // fatal for JavaScript regular expression, so eliminate it. + : '' +) + +// > If the pattern ends with a slash, +// > it is removed for the purpose of the following description, +// > but it would only find a match with a directory. +// > In other words, foo/ will match a directory foo and paths underneath it, +// > but will not match a regular file or a symbolic link foo +// > (this is consistent with the way how pathspec works in general in Git). +// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' +// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call +// you could use option `mark: true` with `glob` + +// '`foo/`' should not continue with the '`..`' +const DEFAULT_REPLACER_PREFIX = [ + + // > Trailing spaces are ignored unless they are quoted with backslash ("\") + [ + // (a\ ) -> (a ) + // (a ) -> (a) + // (a \ ) -> (a ) + /\\?\s+$/, + match => match.indexOf('\\') === 0 + ? ' ' + : '' + ], + + // replace (\ ) with ' ' + [ + /\\\s/g, + () => ' ' + ], + + // Escape metacharacters + // which is written down by users but means special for regular expressions. + + // > There are 12 characters with special meanings: + // > - the backslash \, + // > - the caret ^, + // > - the dollar sign $, + // > - the period or dot ., + // > - the vertical bar or pipe symbol |, + // > - the question mark ?, + // > - the asterisk or star *, + // > - the plus sign +, + // > - the opening parenthesis (, + // > - the closing parenthesis ), + // > - and the opening square bracket [, + // > - the opening curly brace {, + // > These special characters are often called "metacharacters". + [ + /[\\^$.|*+(){]/g, + match => `\\${match}` + ], + + [ + // > [abc] matches any character inside the brackets + // > (in this case a, b, or c); + /\[([^\]/]*)($|\])/g, + (match, p1, p2) => p2 === ']' + ? `[${sanitizeRange(p1)}]` + : `\\${match}` + ], + + [ + // > a question mark (?) matches a single character + /(?!\\)\?/g, + () => '[^/]' + ], + + // leading slash + [ + + // > A leading slash matches the beginning of the pathname. + // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". + // A leading slash matches the beginning of the pathname + /^\//, + () => '^' + ], + + // replace special metacharacter slash after the leading slash + [ + /\//g, + () => '\\/' + ], + + [ + // > A leading "**" followed by a slash means match in all directories. + // > For example, "**/foo" matches file or directory "foo" anywhere, + // > the same as pattern "foo". + // > "**/foo/bar" matches file or directory "bar" anywhere that is directly + // > under directory "foo". + // Notice that the '*'s have been replaced as '\\*' + /^\^*\\\*\\\*\\\//, + + // '**/foo' <-> 'foo' + () => '^(?:.*\\/)?' + ] +] + +const DEFAULT_REPLACER_SUFFIX = [ + // starting + [ + // there will be no leading '/' + // (which has been replaced by section "leading slash") + // If starts with '**', adding a '^' to the regular expression also works + /^(?=[^^])/, + function startingReplacer () { + return !/\/(?!$)/.test(this) + // > If the pattern does not contain a slash /, + // > Git treats it as a shell glob pattern + // Actually, if there is only a trailing slash, + // git also treats it as a shell glob pattern + ? '(?:^|\\/)' + + // > Otherwise, Git treats the pattern as a shell glob suitable for + // > consumption by fnmatch(3) + : '^' + } + ], + + // two globstars + [ + // Use lookahead assertions so that we could match more than one `'/**'` + /\\\/\\\*\\\*(?=\\\/|$)/g, + + // Zero, one or several directories + // should not use '*', or it will be replaced by the next replacer + + // Check if it is not the last `'/**'` + (_, index, str) => index + 6 < str.length + + // case: /**/ + // > A slash followed by two consecutive asterisks then a slash matches + // > zero or more directories. + // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. + // '/**/' + ? '(?:\\/[^\\/]+)*' + + // case: /** + // > A trailing `"/**"` matches everything inside. + + // #21: everything inside but it should not include the current folder + : '\\/.+' + ], + + // intermediate wildcards + [ + // Never replace escaped '*' + // ignore rule '\*' will match the path '*' + + // 'abc.*/' -> go + // 'abc.*' -> skip this rule + /(^|[^\\]+)\\\*(?=.+)/g, + + // '*.js' matches '.js' + // '*.js' doesn't match 'abc' + (_, p1) => `${p1}[^\\/]*` + ], + + // trailing wildcard + [ + /(\^|\\\/)?\\\*$/, + (_, p1) => { + const prefix = p1 + // '\^': + // '/*' does not match '' + // '/*' does not match everything + + // '\\\/': + // 'abc/*' does not match 'abc/' + ? `${p1}[^/]+` + + // 'a*' matches 'a' + // 'a*' matches 'aa' + : '[^/]*' + + return `${prefix}(?=$|\\/$)` + } + ], + + [ + // unescape + /\\\\\\/g, + () => '\\' + ] +] + +const POSITIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + + // 'f' + // matches + // - /f(end) + // - /f/ + // - (start)f(end) + // - (start)f/ + // doesn't match + // - oof + // - foo + // pseudo: + // -> (^|/)f(/|$) + + // ending + [ + // 'js' will not match 'js.' + // 'ab' will not match 'abc' + /(?:[^*/])$/, + + // 'js*' will not match 'a.js' + // 'js/' will not match 'a.js' + // 'js' will match 'a.js' and 'a.js/' + match => `${match}(?=$|\\/)` + ], + + ...DEFAULT_REPLACER_SUFFIX +] + +const NEGATIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + + // #24, #38 + // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) + // A negative pattern without a trailing wildcard should not + // re-include the things inside that directory. + + // eg: + // ['node_modules/*', '!node_modules'] + // should ignore `node_modules/a.js` + [ + /(?:[^*])$/, + match => `${match}(?=$|\\/$)` + ], + + ...DEFAULT_REPLACER_SUFFIX +] + +// A simple cache, because an ignore rule only has only one certain meaning +const regexCache = Object.create(null) + +// @param {pattern} +const makeRegex = (pattern, negative, ignorecase) => { + const r = regexCache[pattern] + if (r) { + return r + } + + const replacers = negative + ? NEGATIVE_REPLACERS + : POSITIVE_REPLACERS + + const source = replacers.reduce( + (prev, current) => prev.replace(current[0], current[1].bind(pattern)), + pattern + ) + + return regexCache[pattern] = ignorecase + ? new RegExp(source, 'i') + : new RegExp(source) +} - if (!rootPkgJson.workspaces) { - return []; - } +const isString = subject => typeof subject === 'string' - const workspacesPathsPatterns = rootPkgJson.workspaces.packages; - let workspaceProjectsPaths = []; +// > A blank line matches no files, so it can serve as a separator for readability. +const checkPattern = pattern => pattern + && isString(pattern) + && !REGEX_TEST_BLANK_LINE.test(pattern) - for (const pattern of workspacesPathsPatterns) { - workspaceProjectsPaths = workspaceProjectsPaths.concat((await packagesFromGlobPattern({ - pattern, - rootPath - }))); - } // Filter out exclude glob patterns + // > A line starting with # serves as a comment. + && pattern.indexOf('#') !== 0 +const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF) - for (const pattern of workspacesPathsPatterns) { - if (pattern.startsWith('!')) { - const pathToRemove = path__WEBPACK_IMPORTED_MODULE_1___default.a.join(rootPath, pattern.slice(1), 'package.json'); - workspaceProjectsPaths = workspaceProjectsPaths.filter(p => p !== pathToRemove); - } +class IgnoreRule { + constructor ( + origin, + pattern, + negative, + regex + ) { + this.origin = origin + this.pattern = pattern + this.negative = negative + this.regex = regex } - - return workspaceProjectsPaths; } -async function copyWorkspacePackages(rootPath) { - const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(rootPath, {}); - const projects = await Object(_projects__WEBPACK_IMPORTED_MODULE_6__["getProjects"])(rootPath, projectPaths); - for (const project of projects.values()) { - const dest = path__WEBPACK_IMPORTED_MODULE_1___default.a.resolve(rootPath, 'node_modules', project.name); +const createRule = (pattern, ignorecase) => { + const origin = pattern + let negative = false - if ((await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["isSymlink"])(dest)) === false) { - continue; - } // Remove the symlink + // > An optional prefix "!" which negates the pattern; + if (pattern.indexOf('!') === 0) { + negative = true + pattern = pattern.substr(1) + } + pattern = pattern + // > Put a backslash ("\") in front of the first "!" for patterns that + // > begin with a literal "!", for example, `"\!important!.txt"`. + .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!') + // > Put a backslash ("\") in front of the first hash for patterns that + // > begin with a hash. + .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#') - await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["unlink"])(dest); // Copy in the package + const regex = makeRegex(pattern, negative, ignorecase) - await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["copyDirectory"])(project.path, dest); - } + return new IgnoreRule( + origin, + pattern, + negative, + regex + ) } -function packagesFromGlobPattern({ - pattern, - rootPath -}) { - const globOptions = { - cwd: rootPath, - // Should throw in case of unusual errors when reading the file system - strict: true, - // Always returns absolute paths for matched files - absolute: true, - // Do not match ** against multiple filenames - // (This is only specified because we currently don't have a need for it.) - noglobstar: true - }; - return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); +const throwError = (message, Ctor) => { + throw new Ctor(message) } -/***/ }), -/* 164 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_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. - */ - - -/** - * Returns all the paths where plugins are located - */ -function getProjectPaths(rootPath, options = {}) { - const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']); - const ossOnly = Boolean(options.oss); - const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared - // plugin functional used in the selenium functional tests. - // As we are now using the webpack dll for the client vendors dependencies - // when we run the plugin functional tests against the distributable - // dependencies used by such plugins like @eui, react and react-dom can't - // be loaded from the dll as the context is different from the one declared - // into the webpack dll reference plugin. - // In anyway, have a plugin declaring their own dependencies is the - // correct and the expect behavior. - - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); +const checkPath = (path, originalPath, doThrow) => { + if (!isString(path)) { + return doThrow( + `path must be a string, but got \`${originalPath}\``, + TypeError + ) + } - if (!ossOnly) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); + // We don't know if we should ignore '', so throw + if (!path) { + return doThrow(`path must not be empty`, TypeError) } - if (!skipKibanaPlugins) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); + // Check if it is a relative path + if (checkPath.isNotRelative(path)) { + const r = '`path.relative()`d' + return doThrow( + `path should be a ${r} string, but got "${originalPath}"`, + RangeError + ) } - return projectPaths; + return true } -/***/ }), -/* 165 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(166); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(181); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); -/* - * 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. - */ - - - - - - -const CleanCommand = { - description: 'Remove the node_modules and target directories from all projects.', - name: 'clean', - - async run(projects) { - const toDelete = []; +const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path) - for (const project of projects.values()) { - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.nodeModulesLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) - }); - } +checkPath.isNotRelative = isNotRelative +checkPath.convert = p => p - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.targetLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) - }); - } +class Ignore { + constructor ({ + ignorecase = true + } = {}) { + this._rules = [] + this._ignorecase = ignorecase + define(this, KEY_IGNORE, true) + this._initCache() + } - const { - extraPatterns - } = project.getCleanConfig(); + _initCache () { + this._ignoreCache = Object.create(null) + this._testCache = Object.create(null) + } - if (extraPatterns) { - toDelete.push({ - cwd: project.path, - pattern: extraPatterns - }); - } + _addPattern (pattern) { + // #32 + if (pattern && pattern[KEY_IGNORE]) { + this._rules = this._rules.concat(pattern._rules) + this._added = true + return } - if (toDelete.length === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.green('\n\nNothing to delete')); - } else { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red('\n\nDeleting:\n')); - /** - * In order to avoid patterns like `/build` in packages from accidentally - * impacting files outside the package we use `process.chdir()` to change - * the cwd to the package and execute `del()` without the `force` option - * so it will check that each file being deleted is within the package. - * - * `del()` does support a `cwd` option, but it's only for resolving the - * patterns and does not impact the cwd check. - */ - - const originalCwd = process.cwd(); - - try { - for (const _ref of toDelete) { - const { - pattern, - cwd - } = _ref; - process.chdir(cwd); - const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); - ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); - await promise; - } - } finally { - process.chdir(originalCwd); - } + if (checkPattern(pattern)) { + const rule = createRule(pattern, this._ignorecase) + this._added = true + this._rules.push(rule) } } -}; - -/***/ }), -/* 166 */ -/***/ (function(module, exports, __webpack_require__) { + // @param {Array | string | Ignore} pattern + add (pattern) { + this._added = false -"use strict"; + makeArray( + isString(pattern) + ? splitPattern(pattern) + : pattern + ).forEach(this._addPattern, this) -const path = __webpack_require__(16); -const globby = __webpack_require__(167); -const isPathCwd = __webpack_require__(174); -const isPathInCwd = __webpack_require__(175); -const pify = __webpack_require__(178); -const rimraf = __webpack_require__(179); -const pMap = __webpack_require__(180); + // Some rules have just added to the ignore, + // making the behavior changed. + if (this._added) { + this._initCache() + } -const rimrafP = pify(rimraf); + return this + } -function safeCheck(file) { - if (isPathCwd(file)) { - throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.'); - } + // legacy + addPattern (pattern) { + return this.add(pattern) + } - if (!isPathInCwd(file)) { - throw new Error('Cannot delete files/folders outside the current working directory. Can be overridden with the `force` option.'); - } -} + // | ignored : unignored + // negative | 0:0 | 0:1 | 1:0 | 1:1 + // -------- | ------- | ------- | ------- | -------- + // 0 | TEST | TEST | SKIP | X + // 1 | TESTIF | SKIP | TEST | X -const del = (patterns, options) => { - options = Object.assign({}, options); + // - SKIP: always skip + // - TEST: always test + // - TESTIF: only test if checkUnignored + // - X: that never happen - const {force, dryRun} = options; - delete options.force; - delete options.dryRun; + // @param {boolean} whether should check if the path is unignored, + // setting `checkUnignored` to `false` could reduce additional + // path matching. - const mapper = file => { - if (!force) { - safeCheck(file); - } + // @returns {TestResult} true if a file is ignored + _testOne (path, checkUnignored) { + let ignored = false + let unignored = false - file = path.resolve(options.cwd || '', file); + this._rules.forEach(rule => { + const {negative} = rule + if ( + unignored === negative && ignored !== unignored + || negative && !ignored && !unignored && !checkUnignored + ) { + return + } - if (dryRun) { - return file; - } + const matched = rule.regex.test(path) - return rimrafP(file, {glob: false}).then(() => file); - }; + if (matched) { + ignored = !negative + unignored = negative + } + }) - return globby(patterns, options).then(files => pMap(files, mapper, options)); -}; + return { + ignored, + unignored + } + } -module.exports = del; -// TODO: Remove this for the next major release -module.exports.default = del; + // @returns {TestResult} + _test (originalPath, cache, checkUnignored, slices) { + const path = originalPath + // Supports nullable path + && checkPath.convert(originalPath) -module.exports.sync = (patterns, options) => { - options = Object.assign({}, options); + checkPath(path, originalPath, throwError) - const {force, dryRun} = options; - delete options.force; - delete options.dryRun; + return this._t(path, cache, checkUnignored, slices) + } - return globby.sync(patterns, options).map(file => { - if (!force) { - safeCheck(file); - } + _t (path, cache, checkUnignored, slices) { + if (path in cache) { + return cache[path] + } - file = path.resolve(options.cwd || '', file); + if (!slices) { + // path/to/a.js + // ['path', 'to', 'a.js'] + slices = path.split(SLASH) + } - if (!dryRun) { - rimraf.sync(file, {glob: false}); - } + slices.pop() - return file; - }); -}; + // If the path has no parent directory, just test it + if (!slices.length) { + return cache[path] = this._testOne(path, checkUnignored) + } + const parent = this._t( + slices.join(SLASH) + SLASH, + cache, + checkUnignored, + slices + ) -/***/ }), -/* 167 */ -/***/ (function(module, exports, __webpack_require__) { + // If the path contains a parent directory, check the parent first + return cache[path] = parent.ignored + // > It is not possible to re-include a file if a parent directory of + // > that file is excluded. + ? parent + : this._testOne(path, checkUnignored) + } -"use strict"; + ignores (path) { + return this._test(path, this._ignoreCache, false).ignored + } -var Promise = __webpack_require__(168); -var arrayUnion = __webpack_require__(170); -var objectAssign = __webpack_require__(172); -var glob = __webpack_require__(37); -var pify = __webpack_require__(173); + createFilter () { + return path => !this.ignores(path) + } -var globP = pify(glob, Promise).bind(glob); + filter (paths) { + return makeArray(paths).filter(this.createFilter()) + } -function isNegative(pattern) { - return pattern[0] === '!'; + // @returns {TestResult} + test (path) { + return this._test(path, this._testCache, true) + } } -function isString(value) { - return typeof value === 'string'; -} +const factory = options => new Ignore(options) -function assertPatternsInput(patterns) { - if (!patterns.every(isString)) { - throw new TypeError('patterns must be a string or an array of strings'); - } -} +const returnFalse = () => false -function generateGlobTasks(patterns, opts) { - patterns = [].concat(patterns); - assertPatternsInput(patterns); +const isPathValid = path => + checkPath(path && checkPath.convert(path), path, returnFalse) - var globTasks = []; +factory.isPathValid = isPathValid - opts = objectAssign({ - cache: Object.create(null), - statCache: Object.create(null), - realpathCache: Object.create(null), - symlinks: Object.create(null), - ignore: [] - }, opts); +// Fixes typescript +factory.default = factory - patterns.forEach(function (pattern, i) { - if (isNegative(pattern)) { - return; - } +module.exports = factory - var ignore = patterns.slice(i).filter(isNegative).map(function (pattern) { - return pattern.slice(1); - }); +// Windows +// -------------------------------------------------------------- +/* istanbul ignore if */ +if ( + // Detect `process` so that it can run in browsers. + typeof process !== 'undefined' + && ( + process.env && process.env.IGNORE_TEST_WIN32 + || process.platform === 'win32' + ) +) { + /* eslint no-control-regex: "off" */ + const makePosix = str => /^\\\\\?\\/.test(str) + || /["<>|\u0000-\u001F]+/u.test(str) + ? str + : str.replace(/\\/g, '/') - globTasks.push({ - pattern: pattern, - opts: objectAssign({}, opts, { - ignore: opts.ignore.concat(ignore) - }) - }); - }); + checkPath.convert = makePosix - return globTasks; + // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/' + // 'd:\\foo' + const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i + checkPath.isNotRelative = path => + REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) + || isNotRelative(path) } -module.exports = function (patterns, opts) { - var globTasks; - - try { - globTasks = generateGlobTasks(patterns, opts); - } catch (err) { - return Promise.reject(err); - } - - return Promise.all(globTasks.map(function (task) { - return globP(task.pattern, task.opts); - })).then(function (paths) { - return arrayUnion.apply(null, paths); - }); -}; - -module.exports.sync = function (patterns, opts) { - var globTasks = generateGlobTasks(patterns, opts); - - return globTasks.reduce(function (matches, task) { - return arrayUnion(matches, glob.sync(task.pattern, task.opts)); - }, []); -}; - -module.exports.generateGlobTasks = generateGlobTasks; - -module.exports.hasMagic = function (patterns, opts) { - return [].concat(patterns).some(function (pattern) { - return glob.hasMagic(pattern, opts); - }); -}; - /***/ }), -/* 168 */ +/* 248 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +module.exports = path => { + const isExtendedLengthPath = /^\\\\\?\\/.test(path); + const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex -module.exports = typeof Promise === 'function' ? Promise : __webpack_require__(169); + if (isExtendedLengthPath || hasNonAscii) { + return path; + } + + return path.replace(/\\/g, '/'); +}; /***/ }), -/* 169 */ +/* 249 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const {Transform} = __webpack_require__(28); -var PENDING = 'pending'; -var SETTLED = 'settled'; -var FULFILLED = 'fulfilled'; -var REJECTED = 'rejected'; -var NOOP = function () {}; -var isNode = typeof global !== 'undefined' && typeof global.process !== 'undefined' && typeof global.process.emit === 'function'; - -var asyncSetTimer = typeof setImmediate === 'undefined' ? setTimeout : setImmediate; -var asyncQueue = []; -var asyncTimer; - -function asyncFlush() { - // run promise callbacks - for (var i = 0; i < asyncQueue.length; i++) { - asyncQueue[i][0](asyncQueue[i][1]); +class ObjectTransform extends Transform { + constructor() { + super({ + objectMode: true + }); } - - // reset async asyncQueue - asyncQueue = []; - asyncTimer = false; } -function asyncCall(callback, arg) { - asyncQueue.push([callback, arg]); +class FilterStream extends ObjectTransform { + constructor(filter) { + super(); + this._filter = filter; + } + + _transform(data, encoding, callback) { + if (this._filter(data)) { + this.push(data); + } - if (!asyncTimer) { - asyncTimer = true; - asyncSetTimer(asyncFlush, 0); + callback(); } } -function invokeResolver(resolver, promise) { - function resolvePromise(value) { - resolve(promise, value); +class UniqueStream extends ObjectTransform { + constructor() { + super(); + this._pushed = new Set(); } - function rejectPromise(reason) { - reject(promise, reason); - } + _transform(data, encoding, callback) { + if (!this._pushed.has(data)) { + this.push(data); + this._pushed.add(data); + } - try { - resolver(resolvePromise, rejectPromise); - } catch (e) { - rejectPromise(e); + callback(); } } -function invokeCallback(subscriber) { - var owner = subscriber.owner; - var settled = owner._state; - var value = owner._data; - var callback = subscriber[settled]; - var promise = subscriber.then; +module.exports = { + FilterStream, + UniqueStream +}; - if (typeof callback === 'function') { - settled = FULFILLED; - try { - value = callback(value); - } catch (e) { - reject(promise, e); - } - } - if (!handleThenable(promise, value)) { - if (settled === FULFILLED) { - resolve(promise, value); - } +/***/ }), +/* 250 */ +/***/ (function(module, exports, __webpack_require__) { - if (settled === REJECTED) { - reject(promise, value); - } - } +var fs = __webpack_require__(23) +var polyfills = __webpack_require__(251) +var legacy = __webpack_require__(252) +var clone = __webpack_require__(253) + +var util = __webpack_require__(29) + +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' } -function handleThenable(promise, value) { - var resolved; +function noop () {} - try { - if (promise === value) { - throw new TypeError('A promises callback cannot return that same promise.'); - } +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } - if (value && (typeof value === 'function' || typeof value === 'object')) { - // then should be retrieved only once - var then = value.then; +// Once time initialization +if (!global[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = [] + Object.defineProperty(global, gracefulQueue, { + get: function() { + return queue + } + }) - if (typeof then === 'function') { - then.call(value, function (val) { - if (!resolved) { - resolved = true; + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + retry() + } - if (value === val) { - fulfill(promise, val); - } else { - resolve(promise, val); - } - } - }, function (reason) { - if (!resolved) { - resolved = true; + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } - reject(promise, reason); - } - }); + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) - return true; - } - } - } catch (e) { - if (!resolved) { - reject(promise, e); - } + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) + retry() + } - return true; - } + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) - return false; + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(global[gracefulQueue]) + __webpack_require__(30).equal(global[gracefulQueue].length, 0) + }) + } } -function resolve(promise, value) { - if (promise === value || !handleThenable(promise, value)) { - fulfill(promise, value); - } +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; } -function fulfill(promise, value) { - if (promise._state === PENDING) { - promise._state = SETTLED; - promise._data = value; +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$readFile(path, options, cb) - asyncCall(publishFulfillment, promise); - } -} + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -function reject(promise, reason) { - if (promise._state === PENDING) { - promise._state = SETTLED; - promise._data = reason; + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null - asyncCall(publishRejection, promise); - } -} + return go$writeFile(path, data, options, cb) -function publish(promise) { - promise._then = promise._then.forEach(invokeCallback); -} + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -function publishFulfillment(promise) { - promise._state = FULFILLED; - publish(promise); -} + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null -function publishRejection(promise) { - promise._state = REJECTED; - publish(promise); - if (!promise._handled && isNode) { - global.process.emit('unhandledRejection', promise._data, promise); - } -} + return go$appendFile(path, data, options, cb) -function notifyRejectionHandled(promise) { - global.process.emit('rejectionHandled', promise); -} + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -/** - * @class - */ -function Promise(resolver) { - if (typeof resolver !== 'function') { - throw new TypeError('Promise resolver ' + resolver + ' is not a function'); - } + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, options, cb) { + var args = [path] + if (typeof options !== 'function') { + args.push(options) + } else { + cb = options + } + args.push(go$readdir$cb) - if (this instanceof Promise === false) { - throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.'); - } + return go$readdir(args) - this._then = []; + function go$readdir$cb (err, files) { + if (files && files.sort) + files.sort() - invokeResolver(resolver, this); -} + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [args]]) -Promise.prototype = { - constructor: Promise, + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + } + } - _state: PENDING, - _then: null, - _data: undefined, - _handled: false, + function go$readdir (args) { + return fs$readdir.apply(fs, args) + } - then: function (onFulfillment, onRejection) { - var subscriber = { - owner: this, - then: new this.constructor(NOOP), - fulfilled: onFulfillment, - rejected: onRejection - }; + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } - if ((onRejection || onFulfillment) && !this._handled) { - this._handled = true; - if (this._state === REJECTED && isNode) { - asyncCall(notifyRejectionHandled, this); - } - } + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } - if (this._state === FULFILLED || this._state === REJECTED) { - // already resolved, call callback async - asyncCall(invokeCallback, subscriber); - } else { - // subscribe - this._then.push(subscriber); - } + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } - return subscriber.then; - }, + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) - catch: function (onRejection) { - return this.then(null, onRejection); - } -}; + // legacy names + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) -Promise.all = function (promises) { - if (!Array.isArray(promises)) { - throw new TypeError('You must pass an array to Promise.all().'); - } + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } - return new Promise(function (resolve, reject) { - var results = []; - var remaining = 0; + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() - function resolver(index) { - remaining++; - return function (value) { - results[index] = value; - if (!--remaining) { - resolve(results); - } - }; - } + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } - for (var i = 0, promise; i < promises.length; i++) { - promise = promises[i]; + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } - if (promise && typeof promise.then === 'function') { - promise.then(resolver(i), reject); - } else { - results[i] = promise; - } - } + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } - if (!remaining) { - resolve(results); - } - }); -}; + function createReadStream (path, options) { + return new fs.ReadStream(path, options) + } -Promise.race = function (promises) { - if (!Array.isArray(promises)) { - throw new TypeError('You must pass an array to Promise.race().'); - } + function createWriteStream (path, options) { + return new fs.WriteStream(path, options) + } - return new Promise(function (resolve, reject) { - for (var i = 0, promise; i < promises.length; i++) { - promise = promises[i]; + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null - if (promise && typeof promise.then === 'function') { - promise.then(resolve, reject); - } else { - resolve(promise); - } - } - }); -}; + return go$open(path, flags, mode, cb) -Promise.resolve = function (value) { - if (value && typeof value === 'object' && value.constructor === Promise) { - return value; - } + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } - return new Promise(function (resolve) { - resolve(value); - }); -}; + return fs +} -Promise.reject = function (reason) { - return new Promise(function (resolve, reject) { - reject(reason); - }); -}; +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + global[gracefulQueue].push(elem) +} -module.exports = Promise; +function retry () { + var elem = global[gracefulQueue].shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} /***/ }), -/* 170 */ +/* 251 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +var constants = __webpack_require__(26) -var arrayUniq = __webpack_require__(171); +var origCwd = process.cwd +var cwd = null -module.exports = function () { - return arrayUniq([].concat.apply([], arguments)); -}; +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} -/***/ }), -/* 171 */ -/***/ (function(module, exports, __webpack_require__) { +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) +} -"use strict"; +module.exports = patch +function patch (fs) { + // (re-)implement some things that are known busted or missing. -// there's 3 implementations written in increasing order of efficiency + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } -// 1 - no Set type is defined -function uniqNoSet(arr) { - var ret = []; + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } - for (var i = 0; i < arr.length; i++) { - if (ret.indexOf(arr[i]) === -1) { - ret.push(arr[i]); - } - } + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. - return ret; -} + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) -// 2 - a simple Set type is defined -function uniqSet(arr) { - var seen = new Set(); - return arr.filter(function (el) { - if (!seen.has(el)) { - seen.add(el); - return true; - } + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) - return false; - }); -} + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) -// 3 - a standard Set type is defined and it has a forEach method -function uniqSetWithForEach(arr) { - var ret = []; + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) - (new Set(arr)).forEach(function (el) { - ret.push(el); - }); + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) - return ret; -} + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) -// V8 currently has a broken implementation -// https://github.com/joyent/node/issues/8449 -function doesForEachActuallyWork() { - var ret = false; + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } - (new Set([true])).forEach(function (el) { - ret = el; - }); + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. - return ret === true; -} + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + }})(fs.rename) + } -if ('Set' in global) { - if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { - module.exports = uniqSetWithForEach; - } else { - module.exports = uniqSet; - } -} else { - module.exports = uniqNoSet; -} + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + // This ensures `util.promisify` works as it does for native `fs.read`. + read.__proto__ = fs$read + return read + })(fs.read) -/***/ }), -/* 172 */ -/***/ (function(module, exports, __webpack_require__) { + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) -"use strict"; -/* -object-assign -(c) Sindre Sorhus -@license MIT -*/ + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) -/* eslint-disable no-unused-vars */ -var getOwnPropertySymbols = Object.getOwnPropertySymbols; -var hasOwnProperty = Object.prototype.hasOwnProperty; -var propIsEnumerable = Object.prototype.propertyIsEnumerable; + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } -function toObject(val) { - if (val === null || val === undefined) { - throw new TypeError('Object.assign cannot be called with null or undefined'); - } + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } - return Object(val); -} + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } -function shouldUseNative() { - try { - if (!Object.assign) { - return false; - } + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } - // Detect buggy property enumeration order in older V8 versions. + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } - // https://bugs.chromium.org/p/v8/issues/detail?id=4118 - var test1 = new String('abc'); // eslint-disable-line no-new-wrappers - test1[5] = 'de'; - if (Object.getOwnPropertyNames(test1)[0] === '5') { - return false; - } + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } - // https://bugs.chromium.org/p/v8/issues/detail?id=3056 - var test2 = {}; - for (var i = 0; i < 10; i++) { - test2['_' + String.fromCharCode(i)] = i; - } - var order2 = Object.getOwnPropertyNames(test2).map(function (n) { - return test2[n]; - }); - if (order2.join('') !== '0123456789') { - return false; - } - // https://bugs.chromium.org/p/v8/issues/detail?id=3056 - var test3 = {}; - 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { - test3[letter] = letter; - }); - if (Object.keys(Object.assign({}, test3)).join('') !== - 'abcdefghijklmnopqrst') { - return false; - } + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } - return true; - } catch (err) { - // We don't expect any of the above to throw, but better to be safe. - return false; - } -} + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } -module.exports = shouldUseNative() ? Object.assign : function (target, source) { - var from; - var to = toObject(target); - var symbols; + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } - for (var s = 1; s < arguments.length; s++) { - from = Object(arguments[s]); + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } + } - for (var key in from) { - if (hasOwnProperty.call(from, key)) { - to[key] = from[key]; - } - } + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true - if (getOwnPropertySymbols) { - symbols = getOwnPropertySymbols(from); - for (var i = 0; i < symbols.length; i++) { - if (propIsEnumerable.call(from, symbols[i])) { - to[symbols[i]] = from[symbols[i]]; - } - } - } - } + if (er.code === "ENOSYS") + return true - return to; -}; + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false + } +} /***/ }), -/* 173 */ +/* 252 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - +var Stream = __webpack_require__(28).Stream -var processFn = function (fn, P, opts) { - return function () { - var that = this; - var args = new Array(arguments.length); +module.exports = legacy - for (var i = 0; i < arguments.length; i++) { - args[i] = arguments[i]; - } +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } - return new P(function (resolve, reject) { - args.push(function (err, result) { - if (err) { - reject(err); - } else if (opts.multiArgs) { - var results = new Array(arguments.length - 1); + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); - for (var i = 1; i < arguments.length; i++) { - results[i - 1] = arguments[i]; - } + Stream.call(this); - resolve(results); - } else { - resolve(result); - } - }); + var self = this; - fn.apply(that, args); - }); - }; -}; + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; -var pify = module.exports = function (obj, P, opts) { - if (typeof P !== 'function') { - opts = P; - P = Promise; - } + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; - opts = opts || {}; - opts.exclude = opts.exclude || [/.+Sync$/]; + options = options || {}; - var filter = function (key) { - var match = function (pattern) { - return typeof pattern === 'string' ? key === pattern : pattern.test(key); - }; + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } - return opts.include ? opts.include.some(match) : !opts.exclude.some(match); - }; + if (this.encoding) this.setEncoding(this.encoding); - var ret = typeof obj === 'function' ? function () { - if (opts.excludeMain) { - return obj.apply(this, arguments); - } + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } - return processFn(obj, P, opts).apply(this, arguments); - } : {}; + if (this.start > this.end) { + throw new Error('start must be <= end'); + } - return Object.keys(obj).reduce(function (ret, key) { - var x = obj[key]; + this.pos = this.start; + } - ret[key] = typeof x === 'function' && filter(key) ? processFn(x, P, opts) : x; + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } - return ret; - }, ret); -}; + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } -pify.all = pify; + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); -/***/ }), -/* 174 */ -/***/ (function(module, exports, __webpack_require__) { + Stream.call(this); -"use strict"; + this.path = path; + this.fd = null; + this.writable = true; -const path = __webpack_require__(16); + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; -module.exports = path_ => path.resolve(path_) === process.cwd(); + options = options || {}; + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } -/***/ }), -/* 175 */ -/***/ (function(module, exports, __webpack_require__) { + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } -"use strict"; + this.pos = this.start; + } -const isPathInside = __webpack_require__(176); + this.busy = false; + this._queue = []; -module.exports = path => isPathInside(path, process.cwd()); + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} /***/ }), -/* 176 */ +/* 253 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const path = __webpack_require__(16); -const pathIsInside = __webpack_require__(177); -module.exports = (childPath, parentPath) => { - childPath = path.resolve(childPath); - parentPath = path.resolve(parentPath); +module.exports = clone - if (childPath === parentPath) { - return false; - } +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj - return pathIsInside(childPath, parentPath); -}; + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} /***/ }), -/* 177 */ +/* 254 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const path = __webpack_require__(16); -var path = __webpack_require__(16); +module.exports = path_ => { + let cwd = process.cwd(); -module.exports = function (thePath, potentialParent) { - // For inside-directory checking, we want to allow trailing slashes, so normalize. - thePath = stripTrailingSep(thePath); - potentialParent = stripTrailingSep(potentialParent); + path_ = path.resolve(path_); - // Node treats only Windows as case-insensitive in its path module; we follow those conventions. - if (process.platform === "win32") { - thePath = thePath.toLowerCase(); - potentialParent = potentialParent.toLowerCase(); - } + if (process.platform === 'win32') { + cwd = cwd.toLowerCase(); + path_ = path_.toLowerCase(); + } - return thePath.lastIndexOf(potentialParent, 0) === 0 && - ( - thePath[potentialParent.length] === path.sep || - thePath[potentialParent.length] === undefined - ); + return path_ === cwd; }; -function stripTrailingSep(thePath) { - if (thePath[thePath.length - 1] === path.sep) { - return thePath.slice(0, -1); - } - return thePath; -} - /***/ }), -/* 178 */ +/* 255 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const path = __webpack_require__(16); -const processFn = (fn, options) => function (...args) { - const P = options.promiseModule; - - return new P((resolve, reject) => { - if (options.multiArgs) { - args.push((...result) => { - if (options.errorFirst) { - if (result[0]) { - reject(result); - } else { - result.shift(); - resolve(result); - } - } else { - resolve(result); - } - }); - } else if (options.errorFirst) { - args.push((error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - } else { - args.push(resolve); - } - - fn.apply(this, args); - }); -}; - -module.exports = (input, options) => { - options = Object.assign({ - exclude: [/.+(Sync|Stream)$/], - errorFirst: true, - promiseModule: Promise - }, options); +module.exports = (childPath, parentPath) => { + childPath = path.resolve(childPath); + parentPath = path.resolve(parentPath); - const objType = typeof input; - if (!(input !== null && (objType === 'object' || objType === 'function'))) { - throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? 'null' : objType}\``); + if (process.platform === 'win32') { + childPath = childPath.toLowerCase(); + parentPath = parentPath.toLowerCase(); } - const filter = key => { - const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); - return options.include ? options.include.some(match) : !options.exclude.some(match); - }; - - let ret; - if (objType === 'function') { - ret = function (...args) { - return options.excludeMain ? input(...args) : processFn(input, options).apply(this, args); - }; - } else { - ret = Object.create(Object.getPrototypeOf(input)); + if (childPath === parentPath) { + return false; } - for (const key in input) { // eslint-disable-line guard-for-in - const property = input[key]; - ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property; - } + childPath += path.sep; + parentPath += path.sep; - return ret; + return childPath.startsWith(parentPath); }; /***/ }), -/* 179 */ +/* 256 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = rimraf -rimraf.sync = rimrafSync - -var assert = __webpack_require__(30) -var path = __webpack_require__(16) -var fs = __webpack_require__(23) -var glob = __webpack_require__(37) -var _0666 = parseInt('666', 8) +const assert = __webpack_require__(30) +const path = __webpack_require__(16) +const fs = __webpack_require__(23) +let glob = undefined +try { + glob = __webpack_require__(37) +} catch (_err) { + // treat glob as optional. +} -var defaultGlobOpts = { +const defaultGlobOpts = { nosort: true, silent: true } // for EMFILE handling -var timeout = 0 +let timeout = 0 -var isWindows = (process.platform === "win32") +const isWindows = (process.platform === "win32") -function defaults (options) { - var methods = [ +const defaults = options => { + const methods = [ 'unlink', 'chmod', 'stat', @@ -24803,7 +31504,7 @@ function defaults (options) { 'rmdir', 'readdir' ] - methods.forEach(function(m) { + methods.forEach(m => { options[m] = options[m] || fs[m] m = m + 'Sync' options[m] = options[m] || fs[m] @@ -24814,11 +31515,14 @@ function defaults (options) { if (options.glob === false) { options.disableGlob = true } + if (options.disableGlob !== true && glob === undefined) { + throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') + } options.disableGlob = options.disableGlob || false options.glob = options.glob || defaultGlobOpts } -function rimraf (p, options, cb) { +const rimraf = (p, options, cb) => { if (typeof options === 'function') { cb = options options = {} @@ -24832,27 +31536,17 @@ function rimraf (p, options, cb) { defaults(options) - var busyTries = 0 - var errState = null - var n = 0 - - if (options.disableGlob || !glob.hasMagic(p)) - return afterGlob(null, [p]) - - options.lstat(p, function (er, stat) { - if (!er) - return afterGlob(null, [p]) - - glob(p, options.glob, afterGlob) - }) + let busyTries = 0 + let errState = null + let n = 0 - function next (er) { + const next = (er) => { errState = errState || er if (--n === 0) cb(errState) } - function afterGlob (er, results) { + const afterGlob = (er, results) => { if (er) return cb(er) @@ -24860,24 +31554,19 @@ function rimraf (p, options, cb) { if (n === 0) return cb() - results.forEach(function (p) { - rimraf_(p, options, function CB (er) { + results.forEach(p => { + const CB = (er) => { if (er) { if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && busyTries < options.maxBusyTries) { busyTries ++ - var time = busyTries * 100 // try again, with the same exact callback as this one. - return setTimeout(function () { - rimraf_(p, options, CB) - }, time) + return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) } // this one won't happen if graceful-fs is used. if (er.code === "EMFILE" && timeout < options.emfileWait) { - return setTimeout(function () { - rimraf_(p, options, CB) - }, timeout ++) + return setTimeout(() => rimraf_(p, options, CB), timeout ++) } // already gone @@ -24886,9 +31575,21 @@ function rimraf (p, options, cb) { timeout = 0 next(er) - }) + } + rimraf_(p, options, CB) }) } + + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) + + options.lstat(p, (er, stat) => { + if (!er) + return afterGlob(null, [p]) + + glob(p, options.glob, afterGlob) + }) + } // Two possible strategies. @@ -24902,14 +31603,14 @@ function rimraf (p, options, cb) { // // If anyone ever complains about this, then I guess the strategy could // be made configurable somehow. But until then, YAGNI. -function rimraf_ (p, options, cb) { +const rimraf_ = (p, options, cb) => { assert(p) assert(options) assert(typeof cb === 'function') // sunos lets the root user unlink directories, which is... weird. // so we have to lstat here and make sure it's not a dir. - options.lstat(p, function (er, st) { + options.lstat(p, (er, st) => { if (er && er.code === "ENOENT") return cb(null) @@ -24920,7 +31621,7 @@ function rimraf_ (p, options, cb) { if (st && st.isDirectory()) return rmdir(p, options, er, cb) - options.unlink(p, function (er) { + options.unlink(p, er => { if (er) { if (er.code === "ENOENT") return cb(null) @@ -24936,18 +31637,18 @@ function rimraf_ (p, options, cb) { }) } -function fixWinEPERM (p, options, er, cb) { +const fixWinEPERM = (p, options, er, cb) => { assert(p) assert(options) assert(typeof cb === 'function') if (er) assert(er instanceof Error) - options.chmod(p, _0666, function (er2) { + options.chmod(p, 0o666, er2 => { if (er2) cb(er2.code === "ENOENT" ? null : er) else - options.stat(p, function(er3, stats) { + options.stat(p, (er3, stats) => { if (er3) cb(er3.code === "ENOENT" ? null : er) else if (stats.isDirectory()) @@ -24958,14 +31659,14 @@ function fixWinEPERM (p, options, er, cb) { }) } -function fixWinEPERMSync (p, options, er) { +const fixWinEPERMSync = (p, options, er) => { assert(p) assert(options) if (er) assert(er instanceof Error) try { - options.chmodSync(p, _0666) + options.chmodSync(p, 0o666) } catch (er2) { if (er2.code === "ENOENT") return @@ -24973,8 +31674,9 @@ function fixWinEPERMSync (p, options, er) { throw er } + let stats try { - var stats = options.statSync(p) + stats = options.statSync(p) } catch (er3) { if (er3.code === "ENOENT") return @@ -24988,7 +31690,7 @@ function fixWinEPERMSync (p, options, er) { options.unlinkSync(p) } -function rmdir (p, options, originalEr, cb) { +const rmdir = (p, options, originalEr, cb) => { assert(p) assert(options) if (originalEr) @@ -24998,7 +31700,7 @@ function rmdir (p, options, originalEr, cb) { // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) // if we guessed wrong, and it's not a directory, then // raise the original error. - options.rmdir(p, function (er) { + options.rmdir(p, er => { if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) rmkids(p, options, cb) else if (er && er.code === "ENOTDIR") @@ -25008,20 +31710,20 @@ function rmdir (p, options, originalEr, cb) { }) } -function rmkids(p, options, cb) { +const rmkids = (p, options, cb) => { assert(p) assert(options) assert(typeof cb === 'function') - options.readdir(p, function (er, files) { + options.readdir(p, (er, files) => { if (er) return cb(er) - var n = files.length + let n = files.length if (n === 0) return options.rmdir(p, cb) - var errState - files.forEach(function (f) { - rimraf(path.join(p, f), options, function (er) { + let errState + files.forEach(f => { + rimraf(path.join(p, f), options, er => { if (errState) return if (er) @@ -25036,7 +31738,7 @@ function rmkids(p, options, cb) { // this looks simpler, and is strictly *faster*, but will // tie up the JavaScript thread and fail on excessively // deep directory trees. -function rimrafSync (p, options) { +const rimrafSync = (p, options) => { options = options || {} defaults(options) @@ -25045,7 +31747,7 @@ function rimrafSync (p, options) { assert(options, 'rimraf: missing options') assert.equal(typeof options, 'object', 'rimraf: options should be object') - var results + let results if (options.disableGlob || !glob.hasMagic(p)) { results = [p] @@ -25061,11 +31763,12 @@ function rimrafSync (p, options) { if (!results.length) return - for (var i = 0; i < results.length; i++) { - var p = results[i] + for (let i = 0; i < results.length; i++) { + const p = results[i] + let st try { - var st = options.lstatSync(p) + st = options.lstatSync(p) } catch (er) { if (er.code === "ENOENT") return @@ -25094,7 +31797,7 @@ function rimrafSync (p, options) { } } -function rmdirSync (p, options, originalEr) { +const rmdirSync = (p, options, originalEr) => { assert(p) assert(options) if (originalEr) @@ -25112,12 +31815,10 @@ function rmdirSync (p, options, originalEr) { } } -function rmkidsSync (p, options) { +const rmkidsSync = (p, options) => { assert(p) assert(options) - options.readdirSync(p).forEach(function (f) { - rimrafSync(path.join(p, f), options) - }) + options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) // We only end up here once we got ENOTEMPTY at least once, and // at this point, we are guaranteed to have removed all the kids. @@ -25125,12 +31826,12 @@ function rmkidsSync (p, options) { // try really hard to delete stuff on windows, because it has a // PROFOUNDLY annoying habit of not closing handles promptly when // files are deleted, resulting in spurious ENOTEMPTY errors. - var retries = isWindows ? 100 : 1 - var i = 0 + const retries = isWindows ? 100 : 1 + let i = 0 do { - var threw = true + let threw = true try { - var ret = options.rmdirSync(p, options) + const ret = options.rmdirSync(p, options) threw = false return ret } finally { @@ -25140,96 +31841,243 @@ function rmkidsSync (p, options) { } while (true) } +module.exports = rimraf +rimraf.sync = rimrafSync + /***/ }), -/* 180 */ +/* 257 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const AggregateError = __webpack_require__(258); -const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { - options = Object.assign({ - concurrency: Infinity - }, options); - - if (typeof mapper !== 'function') { - throw new TypeError('Mapper function is required'); - } +module.exports = async ( + iterable, + mapper, + { + concurrency = Infinity, + stopOnError = true + } = {} +) => { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } - const {concurrency} = options; + if (!(typeof concurrency === 'number' && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); + } - if (!(typeof concurrency === 'number' && concurrency >= 1)) { - throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); - } + const ret = []; + const errors = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; - const ret = []; - const iterator = iterable[Symbol.iterator](); - let isRejected = false; - let isIterableDone = false; - let resolvingCount = 0; - let currentIndex = 0; + const next = () => { + if (isRejected) { + return; + } - const next = () => { - if (isRejected) { - return; - } + const nextItem = iterator.next(); + const i = currentIndex; + currentIndex++; - const nextItem = iterator.next(); - const i = currentIndex; - currentIndex++; + if (nextItem.done) { + isIterableDone = true; - if (nextItem.done) { - isIterableDone = true; + if (resolvingCount === 0) { + if (!stopOnError && errors.length !== 0) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } + } - if (resolvingCount === 0) { - resolve(ret); + return; } - return; - } + resolvingCount++; - resolvingCount++; - - Promise.resolve(nextItem.value) - .then(element => mapper(element, i)) - .then( - value => { - ret[i] = value; + (async () => { + try { + const element = await nextItem.value; + ret[i] = await mapper(element, i); resolvingCount--; next(); - }, - error => { - isRejected = true; - reject(error); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } } - ); - }; + })(); + }; + + for (let i = 0; i < concurrency; i++) { + next(); + + if (isIterableDone) { + break; + } + } + }); +}; + + +/***/ }), +/* 258 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const indentString = __webpack_require__(259); +const cleanStack = __webpack_require__(260); + +const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); - for (let i = 0; i < concurrency; i++) { - next(); +class AggregateError extends Error { + constructor(errors) { + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } + + errors = [...errors].map(error => { + if (error instanceof Error) { + return error; + } + + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } + + return new Error(error); + }); + + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); + + this.name = 'AggregateError'; + + Object.defineProperty(this, '_errors', {value: errors}); + } - if (isIterableDone) { - break; + * [Symbol.iterator]() { + for (const error of this._errors) { + yield error; } } -}); +} -module.exports = pMap; -// TODO: Remove this for the next major release -module.exports.default = pMap; +module.exports = AggregateError; /***/ }), -/* 181 */ +/* 259 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +module.exports = (str, count, opts) => { + // Support older versions: use the third parameter as options.indent + // TODO: Remove the workaround in the next major version + const options = typeof opts === 'object' ? Object.assign({indent: ' '}, opts) : {indent: opts || ' '}; + count = count === undefined ? 1 : count; + + if (typeof str !== 'string') { + throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof str}\``); + } + + if (typeof count !== 'number') { + throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); + } + + if (typeof options.indent !== 'string') { + throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``); + } + + if (count === 0) { + return str; + } + + const regex = options.includeEmptyLines ? /^/mg : /^(?!\s*$)/mg; + return str.replace(regex, options.indent.repeat(count)); +} +; + + +/***/ }), +/* 260 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const os = __webpack_require__(11); + +const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/; +const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/; +const homeDir = os.homedir(); + +module.exports = (stack, options) => { + options = Object.assign({pretty: false}, options); + + return stack.replace(/\\/g, '/') + .split('\n') + .filter(line => { + const pathMatches = line.match(extractPathRegex); + if (pathMatches === null || !pathMatches[1]) { + return true; + } + + const match = pathMatches[1]; + + // Electron + if ( + match.includes('.app/Contents/Resources/electron.asar') || + match.includes('.app/Contents/Resources/default_app.asar') + ) { + return false; + } + + return !pathRegex.test(match); + }) + .filter(line => line.trim() !== '') + .map(line => { + if (options.pretty) { + return line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); + } + + return line; + }) + .join('\n'); +}; + + +/***/ }), +/* 261 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(182); -const cliCursor = __webpack_require__(186); -const cliSpinners = __webpack_require__(190); -const logSymbols = __webpack_require__(150); +const chalk = __webpack_require__(262); +const cliCursor = __webpack_require__(266); +const cliSpinners = __webpack_require__(270); +const logSymbols = __webpack_require__(158); class Ora { constructor(options) { @@ -25376,16 +32224,16 @@ module.exports.promise = (action, options) => { /***/ }), -/* 182 */ +/* 262 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(183); -const stdoutColor = __webpack_require__(184).stdout; +const ansiStyles = __webpack_require__(263); +const stdoutColor = __webpack_require__(264).stdout; -const template = __webpack_require__(185); +const template = __webpack_require__(265); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -25611,7 +32459,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 183 */ +/* 263 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25784,7 +32632,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 184 */ +/* 264 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25926,7 +32774,7 @@ module.exports = { /***/ }), -/* 185 */ +/* 265 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26061,12 +32909,12 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 186 */ +/* 266 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(187); +const restoreCursor = __webpack_require__(267); let hidden = false; @@ -26107,12 +32955,12 @@ exports.toggle = (force, stream) => { /***/ }), -/* 187 */ +/* 267 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(188); +const onetime = __webpack_require__(268); const signalExit = __webpack_require__(110); module.exports = onetime(() => { @@ -26123,12 +32971,12 @@ module.exports = onetime(() => { /***/ }), -/* 188 */ +/* 268 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(189); +const mimicFn = __webpack_require__(269); module.exports = (fn, opts) => { // TODO: Remove this in v3 @@ -26169,7 +33017,7 @@ module.exports = (fn, opts) => { /***/ }), -/* 189 */ +/* 269 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26185,22 +33033,22 @@ module.exports = (to, from) => { /***/ }), -/* 190 */ +/* 270 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(191); +module.exports = __webpack_require__(271); /***/ }), -/* 191 */ +/* 271 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); /***/ }), -/* 192 */ +/* 272 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -26260,7 +33108,7 @@ const RunCommand = { }; /***/ }), -/* 193 */ +/* 273 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -26271,7 +33119,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(35); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(36); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(194); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(274); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -26355,14 +33203,14 @@ const WatchCommand = { }; /***/ }), -/* 194 */ +/* 274 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(195); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(377); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -26429,169 +33277,177 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 195 */ +/* 275 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _internal_Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return _internal_Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]; }); -/* harmony import */ var _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(214); +/* harmony import */ var _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(293); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__["ConnectableObservable"]; }); -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(219); +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(298); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__["GroupedObservable"]; }); -/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(211); +/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(290); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__["observable"]; }); -/* harmony import */ var _internal_Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(215); +/* harmony import */ var _internal_Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(294); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return _internal_Subject__WEBPACK_IMPORTED_MODULE_4__["Subject"]; }); -/* harmony import */ var _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(220); +/* harmony import */ var _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(299); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__["BehaviorSubject"]; }); -/* harmony import */ var _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(221); +/* harmony import */ var _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(300); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__["ReplaySubject"]; }); -/* harmony import */ var _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(238); +/* harmony import */ var _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(317); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__["AsyncSubject"]; }); -/* harmony import */ var _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(239); +/* harmony import */ var _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(318); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asapScheduler", function() { return _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__["asap"]; }); -/* harmony import */ var _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(243); +/* harmony import */ var _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(322); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asyncScheduler", function() { return _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__["async"]; }); -/* harmony import */ var _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(222); +/* harmony import */ var _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(301); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "queueScheduler", function() { return _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__["queue"]; }); -/* harmony import */ var _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(244); +/* harmony import */ var _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(323); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "animationFrameScheduler", function() { return _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__["animationFrame"]; }); -/* harmony import */ var _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(247); +/* harmony import */ var _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(326); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualTimeScheduler"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualAction"]; }); -/* harmony import */ var _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(228); +/* harmony import */ var _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(307); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Scheduler", function() { return _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__["Scheduler"]; }); -/* harmony import */ var _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(204); +/* harmony import */ var _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(284); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__["Subscription"]; }); -/* harmony import */ var _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(198); +/* harmony import */ var _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(278); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__["Subscriber"]; }); -/* harmony import */ var _internal_Notification__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(230); +/* harmony import */ var _internal_Notification__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(309); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["Notification"]; }); -/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(212); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NotificationKind", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["NotificationKind"]; }); + +/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(291); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__["pipe"]; }); -/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(213); +/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(292); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__["noop"]; }); -/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(248); +/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(327); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__["identity"]; }); -/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(249); +/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(328); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__["isObservable"]; }); -/* harmony import */ var _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(250); +/* harmony import */ var _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(329); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__["ArgumentOutOfRangeError"]; }); -/* harmony import */ var _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(251); +/* harmony import */ var _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(330); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__["EmptyError"]; }); -/* harmony import */ var _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(216); +/* harmony import */ var _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(295); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__["ObjectUnsubscribedError"]; }); -/* harmony import */ var _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(209); +/* harmony import */ var _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(287); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__["UnsubscriptionError"]; }); -/* harmony import */ var _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(252); +/* harmony import */ var _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(331); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__["TimeoutError"]; }); -/* harmony import */ var _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(253); +/* harmony import */ var _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(332); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__["bindCallback"]; }); -/* harmony import */ var _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(255); +/* harmony import */ var _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(334); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__["bindNodeCallback"]; }); -/* harmony import */ var _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(256); +/* harmony import */ var _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(335); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__["combineLatest"]; }); -/* harmony import */ var _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(267); +/* harmony import */ var _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(346); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__["concat"]; }); -/* harmony import */ var _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(277); +/* harmony import */ var _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(357); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__["defer"]; }); -/* harmony import */ var _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(231); +/* harmony import */ var _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(310); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["empty"]; }); -/* harmony import */ var _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(278); +/* harmony import */ var _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(358); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__["forkJoin"]; }); -/* harmony import */ var _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(268); +/* harmony import */ var _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(350); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "from", function() { return _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__["from"]; }); -/* harmony import */ var _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(279); +/* harmony import */ var _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(359); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__["fromEvent"]; }); -/* harmony import */ var _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(280); +/* harmony import */ var _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(360); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__["fromEventPattern"]; }); -/* harmony import */ var _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(281); +/* harmony import */ var _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(361); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__["generate"]; }); -/* harmony import */ var _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(282); +/* harmony import */ var _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(362); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__["iif"]; }); -/* harmony import */ var _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(283); +/* harmony import */ var _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(363); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__["interval"]; }); -/* harmony import */ var _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(285); +/* harmony import */ var _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(365); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__["merge"]; }); -/* harmony import */ var _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(286); +/* harmony import */ var _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(366); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "never", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["never"]; }); -/* harmony import */ var _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(232); +/* harmony import */ var _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(311); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "of", function() { return _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__["of"]; }); -/* harmony import */ var _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(287); +/* harmony import */ var _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(367); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(288); +/* harmony import */ var _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(368); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__["pairs"]; }); -/* harmony import */ var _internal_observable_race__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(289); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_observable_race__WEBPACK_IMPORTED_MODULE_44__["race"]; }); +/* harmony import */ var _internal_observable_partition__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(369); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_observable_partition__WEBPACK_IMPORTED_MODULE_44__["partition"]; }); + +/* harmony import */ var _internal_observable_race__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(372); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_observable_race__WEBPACK_IMPORTED_MODULE_45__["race"]; }); + +/* harmony import */ var _internal_observable_range__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(373); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "range", function() { return _internal_observable_range__WEBPACK_IMPORTED_MODULE_46__["range"]; }); -/* harmony import */ var _internal_observable_range__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(290); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "range", function() { return _internal_observable_range__WEBPACK_IMPORTED_MODULE_45__["range"]; }); +/* harmony import */ var _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(316); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_47__["throwError"]; }); -/* harmony import */ var _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(237); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_46__["throwError"]; }); +/* harmony import */ var _internal_observable_timer__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(374); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return _internal_observable_timer__WEBPACK_IMPORTED_MODULE_48__["timer"]; }); -/* harmony import */ var _internal_observable_timer__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(291); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return _internal_observable_timer__WEBPACK_IMPORTED_MODULE_47__["timer"]; }); +/* harmony import */ var _internal_observable_using__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(375); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "using", function() { return _internal_observable_using__WEBPACK_IMPORTED_MODULE_49__["using"]; }); -/* harmony import */ var _internal_observable_using__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(292); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "using", function() { return _internal_observable_using__WEBPACK_IMPORTED_MODULE_48__["using"]; }); +/* harmony import */ var _internal_observable_zip__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(376); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_observable_zip__WEBPACK_IMPORTED_MODULE_50__["zip"]; }); -/* harmony import */ var _internal_observable_zip__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(293); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_observable_zip__WEBPACK_IMPORTED_MODULE_49__["zip"]; }); +/* harmony import */ var _internal_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(351); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scheduled", function() { return _internal_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_51__["scheduled"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["EMPTY"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["NEVER"]; }); -/* harmony import */ var _internal_config__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(202); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "config", function() { return _internal_config__WEBPACK_IMPORTED_MODULE_50__["config"]; }); +/* harmony import */ var _internal_config__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(282); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "config", function() { return _internal_config__WEBPACK_IMPORTED_MODULE_52__["config"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -26644,6 +33500,8 @@ __webpack_require__.r(__webpack_exports__); + + @@ -26651,17 +33509,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 196 */ +/* 276 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return Observable; }); -/* harmony import */ var _util_toSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(197); -/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(211); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(212); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(202); -/** PURE_IMPORTS_START _util_toSubscriber,_internal_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ +/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(277); +/* harmony import */ var _util_toSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(289); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(290); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(291); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(282); +/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ + @@ -26681,16 +33541,16 @@ var Observable = /*@__PURE__*/ (function () { }; Observable.prototype.subscribe = function (observerOrNext, error, complete) { var operator = this.operator; - var sink = Object(_util_toSubscriber__WEBPACK_IMPORTED_MODULE_0__["toSubscriber"])(observerOrNext, error, complete); + var sink = Object(_util_toSubscriber__WEBPACK_IMPORTED_MODULE_1__["toSubscriber"])(observerOrNext, error, complete); if (operator) { - operator.call(sink, this.source); + sink.add(operator.call(sink, this.source)); } else { - sink.add(this.source || (_config__WEBPACK_IMPORTED_MODULE_3__["config"].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? + sink.add(this.source || (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? this._subscribe(sink) : this._trySubscribe(sink)); } - if (_config__WEBPACK_IMPORTED_MODULE_3__["config"].useDeprecatedSynchronousErrorHandling) { + if (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling) { if (sink.syncErrorThrowable) { sink.syncErrorThrowable = false; if (sink.syncErrorThrown) { @@ -26705,11 +33565,16 @@ var Observable = /*@__PURE__*/ (function () { return this._subscribe(sink); } catch (err) { - if (_config__WEBPACK_IMPORTED_MODULE_3__["config"].useDeprecatedSynchronousErrorHandling) { + if (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling) { sink.syncErrorThrown = true; sink.syncErrorValue = err; } - sink.error(err); + if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_0__["canReportError"])(sink)) { + sink.error(err); + } + else { + console.warn(err); + } } }; Observable.prototype.forEach = function (next, promiseCtor) { @@ -26734,7 +33599,7 @@ var Observable = /*@__PURE__*/ (function () { var source = this.source; return source && source.subscribe(subscriber); }; - Observable.prototype[_internal_symbol_observable__WEBPACK_IMPORTED_MODULE_1__["observable"]] = function () { + Observable.prototype[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]] = function () { return this; }; Observable.prototype.pipe = function () { @@ -26745,7 +33610,7 @@ var Observable = /*@__PURE__*/ (function () { if (operations.length === 0) { return this; } - return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_2__["pipeFromArray"])(operations)(this); + return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipeFromArray"])(operations)(this); }; Observable.prototype.toPromise = function (promiseCtor) { var _this = this; @@ -26763,7 +33628,7 @@ var Observable = /*@__PURE__*/ (function () { function getPromiseCtor(promiseCtor) { if (!promiseCtor) { - promiseCtor = _config__WEBPACK_IMPORTED_MODULE_3__["config"].Promise || Promise; + promiseCtor = _config__WEBPACK_IMPORTED_MODULE_4__["config"].Promise || Promise; } if (!promiseCtor) { throw new Error('no Promise impl found'); @@ -26774,50 +33639,48 @@ function getPromiseCtor(promiseCtor) { /***/ }), -/* 197 */ +/* 277 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toSubscriber", function() { return toSubscriber; }); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(198); -/* harmony import */ var _symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(210); -/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(201); -/** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */ - - - -function toSubscriber(nextOrObserver, error, complete) { - if (nextOrObserver) { - if (nextOrObserver instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { - return nextOrObserver; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "canReportError", function() { return canReportError; }); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(278); +/** PURE_IMPORTS_START _Subscriber PURE_IMPORTS_END */ + +function canReportError(observer) { + while (observer) { + var _a = observer, closed_1 = _a.closed, destination = _a.destination, isStopped = _a.isStopped; + if (closed_1 || isStopped) { + return false; } - if (nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]]) { - return nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]](); + else if (destination && destination instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { + observer = destination; + } + else { + observer = null; } } - if (!nextOrObserver && !error && !complete) { - return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](_Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]); - } - return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](nextOrObserver, error, complete); + return true; } -//# sourceMappingURL=toSubscriber.js.map +//# sourceMappingURL=canReportError.js.map /***/ }), -/* 198 */ +/* 278 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return Subscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(200); -/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(201); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(204); -/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(210); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(202); -/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(203); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SafeSubscriber", function() { return SafeSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(280); +/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(281); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(288); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(282); +/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(283); /** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ @@ -26844,11 +33707,10 @@ var Subscriber = /*@__PURE__*/ (function (_super) { break; } if (typeof destinationOrNext === 'object') { - if (isTrustedSubscriber(destinationOrNext)) { - var trustedSubscriber = destinationOrNext[_internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__["rxSubscriber"]](); - _this.syncErrorThrowable = trustedSubscriber.syncErrorThrowable; - _this.destination = trustedSubscriber; - trustedSubscriber.add(_this); + if (destinationOrNext instanceof Subscriber) { + _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable; + _this.destination = destinationOrNext; + destinationOrNext.add(_this); } else { _this.syncErrorThrowable = true; @@ -26905,14 +33767,12 @@ var Subscriber = /*@__PURE__*/ (function (_super) { this.unsubscribe(); }; Subscriber.prototype._unsubscribeAndRecycle = function () { - var _a = this, _parent = _a._parent, _parents = _a._parents; - this._parent = null; - this._parents = null; + var _parentOrParents = this._parentOrParents; + this._parentOrParents = null; this.unsubscribe(); this.closed = false; this.isStopped = false; - this._parent = _parent; - this._parents = _parents; + this._parentOrParents = _parentOrParents; return this; }; return Subscriber; @@ -27052,14 +33912,12 @@ var SafeSubscriber = /*@__PURE__*/ (function (_super) { }; return SafeSubscriber; }(Subscriber)); -function isTrustedSubscriber(obj) { - return obj instanceof Subscriber || ('syncErrorThrowable' in obj && obj[_internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__["rxSubscriber"]]); -} + //# sourceMappingURL=Subscriber.js.map /***/ }), -/* 199 */ +/* 279 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27272,7 +34130,7 @@ function __importDefault(mod) { /***/ }), -/* 200 */ +/* 280 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27286,14 +34144,14 @@ function isFunction(x) { /***/ }), -/* 201 */ +/* 281 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(202); -/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(203); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(282); +/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(283); /** PURE_IMPORTS_START _config,_util_hostReportError PURE_IMPORTS_END */ @@ -27314,7 +34172,7 @@ var empty = { /***/ }), -/* 202 */ +/* 282 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27342,7 +34200,7 @@ var config = { /***/ }), -/* 203 */ +/* 283 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27350,27 +34208,23 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hostReportError", function() { return hostReportError; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ function hostReportError(err) { - setTimeout(function () { throw err; }); + setTimeout(function () { throw err; }, 0); } //# sourceMappingURL=hostReportError.js.map /***/ }), -/* 204 */ +/* 284 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return Subscription; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); -/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(206); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(208); -/* harmony import */ var _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(209); -/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_tryCatch,_util_errorObject,_util_UnsubscriptionError PURE_IMPORTS_END */ - - +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(286); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); +/* harmony import */ var _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(287); +/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_UnsubscriptionError PURE_IMPORTS_END */ @@ -27378,94 +34232,112 @@ __webpack_require__.r(__webpack_exports__); var Subscription = /*@__PURE__*/ (function () { function Subscription(unsubscribe) { this.closed = false; - this._parent = null; - this._parents = null; + this._parentOrParents = null; this._subscriptions = null; if (unsubscribe) { this._unsubscribe = unsubscribe; } } Subscription.prototype.unsubscribe = function () { - var hasErrors = false; var errors; if (this.closed) { return; } - var _a = this, _parent = _a._parent, _parents = _a._parents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; + var _a = this, _parentOrParents = _a._parentOrParents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; this.closed = true; - this._parent = null; - this._parents = null; + this._parentOrParents = null; this._subscriptions = null; - var index = -1; - var len = _parents ? _parents.length : 0; - while (_parent) { - _parent.remove(this); - _parent = ++index < len && _parents[index] || null; + if (_parentOrParents instanceof Subscription) { + _parentOrParents.remove(this); + } + else if (_parentOrParents !== null) { + for (var index = 0; index < _parentOrParents.length; ++index) { + var parent_1 = _parentOrParents[index]; + parent_1.remove(this); + } } if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(_unsubscribe)) { - var trial = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_3__["tryCatch"])(_unsubscribe).call(this); - if (trial === _util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"]) { - hasErrors = true; - errors = errors || (_util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"].e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__["UnsubscriptionError"] ? - flattenUnsubscriptionErrors(_util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"].e.errors) : [_util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"].e]); + try { + _unsubscribe.call(this); + } + catch (e) { + errors = e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"] ? flattenUnsubscriptionErrors(e.errors) : [e]; } } if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(_subscriptions)) { - index = -1; - len = _subscriptions.length; + var index = -1; + var len = _subscriptions.length; while (++index < len) { var sub = _subscriptions[index]; if (Object(_util_isObject__WEBPACK_IMPORTED_MODULE_1__["isObject"])(sub)) { - var trial = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_3__["tryCatch"])(sub.unsubscribe).call(sub); - if (trial === _util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"]) { - hasErrors = true; + try { + sub.unsubscribe(); + } + catch (e) { errors = errors || []; - var err = _util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"].e; - if (err instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__["UnsubscriptionError"]) { - errors = errors.concat(flattenUnsubscriptionErrors(err.errors)); + if (e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"]) { + errors = errors.concat(flattenUnsubscriptionErrors(e.errors)); } else { - errors.push(err); + errors.push(e); } } } } } - if (hasErrors) { - throw new _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__["UnsubscriptionError"](errors); + if (errors) { + throw new _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"](errors); } }; Subscription.prototype.add = function (teardown) { - if (!teardown || (teardown === Subscription.EMPTY)) { + var subscription = teardown; + if (!teardown) { return Subscription.EMPTY; } - if (teardown === this) { - return this; - } - var subscription = teardown; switch (typeof teardown) { case 'function': subscription = new Subscription(teardown); case 'object': - if (subscription.closed || typeof subscription.unsubscribe !== 'function') { + if (subscription === this || subscription.closed || typeof subscription.unsubscribe !== 'function') { return subscription; } else if (this.closed) { subscription.unsubscribe(); return subscription; } - else if (typeof subscription._addParent !== 'function') { + else if (!(subscription instanceof Subscription)) { var tmp = subscription; subscription = new Subscription(); subscription._subscriptions = [tmp]; } break; - default: + default: { throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.'); + } + } + var _parentOrParents = subscription._parentOrParents; + if (_parentOrParents === null) { + subscription._parentOrParents = this; + } + else if (_parentOrParents instanceof Subscription) { + if (_parentOrParents === this) { + return subscription; + } + subscription._parentOrParents = [_parentOrParents, this]; + } + else if (_parentOrParents.indexOf(this) === -1) { + _parentOrParents.push(this); + } + else { + return subscription; + } + var subscriptions = this._subscriptions; + if (subscriptions === null) { + this._subscriptions = [subscription]; + } + else { + subscriptions.push(subscription); } - var subscriptions = this._subscriptions || (this._subscriptions = []); - subscriptions.push(subscription); - subscription._addParent(this); return subscription; }; Subscription.prototype.remove = function (subscription) { @@ -27477,18 +34349,6 @@ var Subscription = /*@__PURE__*/ (function () { } } }; - Subscription.prototype._addParent = function (parent) { - var _a = this, _parent = _a._parent, _parents = _a._parents; - if (!_parent || _parent === parent) { - this._parent = parent; - } - else if (!_parents) { - this._parents = [parent]; - } - else if (_parents.indexOf(parent) === -1) { - _parents.push(parent); - } - }; Subscription.EMPTY = (function (empty) { empty.closed = true; return empty; @@ -27497,25 +34357,25 @@ var Subscription = /*@__PURE__*/ (function () { }()); function flattenUnsubscriptionErrors(errors) { - return errors.reduce(function (errs, err) { return errs.concat((err instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__["UnsubscriptionError"]) ? err.errors : err); }, []); + return errors.reduce(function (errs, err) { return errs.concat((err instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"]) ? err.errors : err); }, []); } //# sourceMappingURL=Subscription.js.map /***/ }), -/* 205 */ +/* 285 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isArray", function() { return isArray; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ -var isArray = Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); +var isArray = /*@__PURE__*/ (function () { return Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); })(); //# sourceMappingURL=isArray.js.map /***/ }), -/* 206 */ +/* 286 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27523,113 +34383,105 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObject", function() { return isObject; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ function isObject(x) { - return x != null && typeof x === 'object'; + return x !== null && typeof x === 'object'; } //# sourceMappingURL=isObject.js.map /***/ }), -/* 207 */ +/* 287 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tryCatch", function() { return tryCatch; }); -/* harmony import */ var _errorObject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(208); -/** PURE_IMPORTS_START _errorObject PURE_IMPORTS_END */ - -var tryCatchTarget; -function tryCatcher() { - try { - return tryCatchTarget.apply(this, arguments); - } - catch (e) { - _errorObject__WEBPACK_IMPORTED_MODULE_0__["errorObject"].e = e; - return _errorObject__WEBPACK_IMPORTED_MODULE_0__["errorObject"]; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return UnsubscriptionError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var UnsubscriptionErrorImpl = /*@__PURE__*/ (function () { + function UnsubscriptionErrorImpl(errors) { + Error.call(this); + this.message = errors ? + errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ') : ''; + this.name = 'UnsubscriptionError'; + this.errors = errors; + return this; } -} -function tryCatch(fn) { - tryCatchTarget = fn; - return tryCatcher; -} -//# sourceMappingURL=tryCatch.js.map + UnsubscriptionErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return UnsubscriptionErrorImpl; +})(); +var UnsubscriptionError = UnsubscriptionErrorImpl; +//# sourceMappingURL=UnsubscriptionError.js.map /***/ }), -/* 208 */ +/* 288 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "errorObject", function() { return errorObject; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "rxSubscriber", function() { return rxSubscriber; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$$rxSubscriber", function() { return $$rxSubscriber; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ -var errorObject = { e: {} }; -//# sourceMappingURL=errorObject.js.map +var rxSubscriber = /*@__PURE__*/ (function () { + return typeof Symbol === 'function' + ? /*@__PURE__*/ Symbol('rxSubscriber') + : '@@rxSubscriber_' + /*@__PURE__*/ Math.random(); +})(); +var $$rxSubscriber = rxSubscriber; +//# sourceMappingURL=rxSubscriber.js.map /***/ }), -/* 209 */ +/* 289 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return UnsubscriptionError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ - -var UnsubscriptionError = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](UnsubscriptionError, _super); - function UnsubscriptionError(errors) { - var _this = _super.call(this, errors ? - errors.length + " errors occurred during unsubscription:\n " + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ') : '') || this; - _this.errors = errors; - _this.name = 'UnsubscriptionError'; - Object.setPrototypeOf(_this, UnsubscriptionError.prototype); - return _this; - } - return UnsubscriptionError; -}(Error)); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toSubscriber", function() { return toSubscriber; }); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(278); +/* harmony import */ var _symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(288); +/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(281); +/** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */ -//# sourceMappingURL=UnsubscriptionError.js.map -/***/ }), -/* 210 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "rxSubscriber", function() { return rxSubscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$$rxSubscriber", function() { return $$rxSubscriber; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var rxSubscriber = (typeof Symbol === 'function' && typeof Symbol.for === 'function') - ? /*@__PURE__*/ Symbol.for('rxSubscriber') - : '@@rxSubscriber'; -var $$rxSubscriber = rxSubscriber; -//# sourceMappingURL=rxSubscriber.js.map +function toSubscriber(nextOrObserver, error, complete) { + if (nextOrObserver) { + if (nextOrObserver instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { + return nextOrObserver; + } + if (nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]]) { + return nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]](); + } + } + if (!nextOrObserver && !error && !complete) { + return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](_Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]); + } + return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](nextOrObserver, error, complete); +} +//# sourceMappingURL=toSubscriber.js.map /***/ }), -/* 211 */ +/* 290 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return observable; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ -var observable = typeof Symbol === 'function' && Symbol.observable || '@@observable'; +var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })(); //# sourceMappingURL=observable.js.map /***/ }), -/* 212 */ +/* 291 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return pipe; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipeFromArray", function() { return pipeFromArray; }); -/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(213); +/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(292); /** PURE_IMPORTS_START _noop PURE_IMPORTS_END */ function pipe() { @@ -27654,7 +34506,7 @@ function pipeFromArray(fns) { /***/ }), -/* 213 */ +/* 292 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27666,19 +34518,19 @@ function noop() { } /***/ }), -/* 214 */ +/* 293 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return ConnectableObservable; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "connectableObservableDescriptor", function() { return connectableObservableDescriptor; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(196); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(204); -/* harmony import */ var _operators_refCount__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(218); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(284); +/* harmony import */ var _operators_refCount__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(297); /** PURE_IMPORTS_START tslib,_Subject,_Observable,_Subscriber,_Subscription,_operators_refCount PURE_IMPORTS_END */ @@ -27717,9 +34569,6 @@ var ConnectableObservable = /*@__PURE__*/ (function (_super) { this._connection = null; connection = _Subscription__WEBPACK_IMPORTED_MODULE_4__["Subscription"].EMPTY; } - else { - this._connection = connection; - } } return connection; }; @@ -27729,18 +34578,20 @@ var ConnectableObservable = /*@__PURE__*/ (function (_super) { return ConnectableObservable; }(_Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"])); -var connectableProto = ConnectableObservable.prototype; -var connectableObservableDescriptor = { - operator: { value: null }, - _refCount: { value: 0, writable: true }, - _subject: { value: null, writable: true }, - _connection: { value: null, writable: true }, - _subscribe: { value: connectableProto._subscribe }, - _isComplete: { value: connectableProto._isComplete, writable: true }, - getSubject: { value: connectableProto.getSubject }, - connect: { value: connectableProto.connect }, - refCount: { value: connectableProto.refCount } -}; +var connectableObservableDescriptor = /*@__PURE__*/ (function () { + var connectableProto = ConnectableObservable.prototype; + return { + operator: { value: null }, + _refCount: { value: 0, writable: true }, + _subject: { value: null, writable: true }, + _connection: { value: null, writable: true }, + _subscribe: { value: connectableProto._subscribe }, + _isComplete: { value: connectableProto._isComplete, writable: true }, + getSubject: { value: connectableProto.getSubject }, + connect: { value: connectableProto.connect }, + refCount: { value: connectableProto.refCount } + }; +})(); var ConnectableSubscriber = /*@__PURE__*/ (function (_super) { tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ConnectableSubscriber, _super); function ConnectableSubscriber(destination, connectable) { @@ -27825,7 +34676,7 @@ var RefCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 215 */ +/* 294 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27833,13 +34684,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscriber", function() { return SubjectSubscriber; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return Subject; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnonymousSubject", function() { return AnonymousSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(196); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(204); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(216); -/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(217); -/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(210); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(295); +/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(296); +/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(288); /** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ @@ -28001,38 +34852,36 @@ var AnonymousSubject = /*@__PURE__*/ (function (_super) { /***/ }), -/* 216 */ +/* 295 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return ObjectUnsubscribedError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ - -var ObjectUnsubscribedError = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ObjectUnsubscribedError, _super); - function ObjectUnsubscribedError() { - var _this = _super.call(this, 'object unsubscribed') || this; - _this.name = 'ObjectUnsubscribedError'; - Object.setPrototypeOf(_this, ObjectUnsubscribedError.prototype); - return _this; +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var ObjectUnsubscribedErrorImpl = /*@__PURE__*/ (function () { + function ObjectUnsubscribedErrorImpl() { + Error.call(this); + this.message = 'object unsubscribed'; + this.name = 'ObjectUnsubscribedError'; + return this; } - return ObjectUnsubscribedError; -}(Error)); - + ObjectUnsubscribedErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return ObjectUnsubscribedErrorImpl; +})(); +var ObjectUnsubscribedError = ObjectUnsubscribedErrorImpl; //# sourceMappingURL=ObjectUnsubscribedError.js.map /***/ }), -/* 217 */ +/* 296 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscription", function() { return SubjectSubscription; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ @@ -28068,14 +34917,14 @@ var SubjectSubscription = /*@__PURE__*/ (function (_super) { /***/ }), -/* 218 */ +/* 297 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return refCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -28137,18 +34986,18 @@ var RefCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 219 */ +/* 298 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return groupBy; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return GroupedObservable; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(196); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(215); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(276); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(294); /** PURE_IMPORTS_START tslib,_Subscriber,_Subscription,_Observable,_Subject PURE_IMPORTS_END */ @@ -28334,15 +35183,15 @@ var InnerRefCountSubscription = /*@__PURE__*/ (function (_super) { /***/ }), -/* 220 */ +/* 299 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return BehaviorSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(216); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(295); /** PURE_IMPORTS_START tslib,_Subject,_util_ObjectUnsubscribedError PURE_IMPORTS_END */ @@ -28389,19 +35238,19 @@ var BehaviorSubject = /*@__PURE__*/ (function (_super) { /***/ }), -/* 221 */ +/* 300 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return ReplaySubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(222); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(204); -/* harmony import */ var _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(229); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(216); -/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(217); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(301); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(308); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(295); +/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(296); /** PURE_IMPORTS_START tslib,_Subject,_scheduler_queue,_Subscription,_operators_observeOn,_util_ObjectUnsubscribedError,_SubjectSubscription PURE_IMPORTS_END */ @@ -28522,14 +35371,14 @@ var ReplayEvent = /*@__PURE__*/ (function () { /***/ }), -/* 222 */ +/* 301 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "queue", function() { return queue; }); -/* harmony import */ var _QueueAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(223); -/* harmony import */ var _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(226); +/* harmony import */ var _QueueAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(302); +/* harmony import */ var _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(305); /** PURE_IMPORTS_START _QueueAction,_QueueScheduler PURE_IMPORTS_END */ @@ -28538,14 +35387,14 @@ var queue = /*@__PURE__*/ new _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__["Queu /***/ }), -/* 223 */ +/* 302 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueAction", function() { return QueueAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(224); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(303); /** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ @@ -28590,14 +35439,14 @@ var QueueAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 224 */ +/* 303 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncAction", function() { return AsyncAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Action__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(225); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Action__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(304); /** PURE_IMPORTS_START tslib,_Action PURE_IMPORTS_END */ @@ -28641,7 +35490,8 @@ var AsyncAction = /*@__PURE__*/ (function (_super) { if (delay !== null && this.delay === delay && this.pending === false) { return id; } - return clearInterval(id) && undefined || undefined; + clearInterval(id); + return undefined; }; AsyncAction.prototype.execute = function (state, delay) { if (this.closed) { @@ -28695,14 +35545,14 @@ var AsyncAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 225 */ +/* 304 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Action", function() { return Action; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ @@ -28724,14 +35574,14 @@ var Action = /*@__PURE__*/ (function (_super) { /***/ }), -/* 226 */ +/* 305 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueScheduler", function() { return QueueScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); /** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ @@ -28747,14 +35597,14 @@ var QueueScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 227 */ +/* 306 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncScheduler", function() { return AsyncScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Scheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(228); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Scheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(307); /** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */ @@ -28816,7 +35666,7 @@ var AsyncScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 228 */ +/* 307 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -28836,7 +35686,7 @@ var Scheduler = /*@__PURE__*/ (function () { } return new this.SchedulerAction(this, work).schedule(state, delay); }; - Scheduler.now = Date.now ? Date.now : function () { return +new Date(); }; + Scheduler.now = function () { return Date.now(); }; return Scheduler; }()); @@ -28844,7 +35694,7 @@ var Scheduler = /*@__PURE__*/ (function () { /***/ }), -/* 229 */ +/* 308 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -28853,9 +35703,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnOperator", function() { return ObserveOnOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnSubscriber", function() { return ObserveOnSubscriber; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnMessage", function() { return ObserveOnMessage; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(230); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(309); /** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -28899,16 +35749,19 @@ var ObserveOnSubscriber = /*@__PURE__*/ (function (_super) { this.unsubscribe(); }; ObserveOnSubscriber.prototype.scheduleMessage = function (notification) { - this.add(this.scheduler.schedule(ObserveOnSubscriber.dispatch, this.delay, new ObserveOnMessage(notification, this.destination))); + var destination = this.destination; + destination.add(this.scheduler.schedule(ObserveOnSubscriber.dispatch, this.delay, new ObserveOnMessage(notification, this.destination))); }; ObserveOnSubscriber.prototype._next = function (value) { this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createNext(value)); }; ObserveOnSubscriber.prototype._error = function (err) { this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createError(err)); + this.unsubscribe(); }; ObserveOnSubscriber.prototype._complete = function () { this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createComplete()); + this.unsubscribe(); }; return ObserveOnSubscriber; }(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); @@ -28925,19 +35778,26 @@ var ObserveOnMessage = /*@__PURE__*/ (function () { /***/ }), -/* 230 */ +/* 309 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NotificationKind", function() { return NotificationKind; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return Notification; }); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(231); -/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(237); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(310); +/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(311); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(316); /** PURE_IMPORTS_START _observable_empty,_observable_of,_observable_throwError PURE_IMPORTS_END */ +var NotificationKind; +/*@__PURE__*/ (function (NotificationKind) { + NotificationKind["NEXT"] = "N"; + NotificationKind["ERROR"] = "E"; + NotificationKind["COMPLETE"] = "C"; +})(NotificationKind || (NotificationKind = {})); var Notification = /*@__PURE__*/ (function () { function Notification(kind, value, error) { this.kind = kind; @@ -29007,15 +35867,14 @@ var Notification = /*@__PURE__*/ (function () { /***/ }), -/* 231 */ +/* 310 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return EMPTY; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "emptyScheduled", function() { return emptyScheduled; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ var EMPTY = /*@__PURE__*/ new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return subscriber.complete(); }); @@ -29029,18 +35888,16 @@ function emptyScheduled(scheduler) { /***/ }), -/* 232 */ +/* 311 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "of", function() { return of; }); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(233); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(234); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); -/* harmony import */ var _scalar__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(236); -/** PURE_IMPORTS_START _util_isScheduler,_fromArray,_empty,_scalar PURE_IMPORTS_END */ - +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(312); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(313); +/* harmony import */ var _scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); +/** PURE_IMPORTS_START _util_isScheduler,_fromArray,_scheduled_scheduleArray PURE_IMPORTS_END */ @@ -29052,24 +35909,17 @@ function of() { var scheduler = args[args.length - 1]; if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_0__["isScheduler"])(scheduler)) { args.pop(); + return Object(_scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(args, scheduler); } else { - scheduler = undefined; - } - switch (args.length) { - case 0: - return Object(_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(scheduler); - case 1: - return scheduler ? Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(args, scheduler) : Object(_scalar__WEBPACK_IMPORTED_MODULE_3__["scalar"])(args[0]); - default: - return Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(args, scheduler); + return Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(args); } } //# sourceMappingURL=of.js.map /***/ }), -/* 233 */ +/* 312 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29083,46 +35933,32 @@ function isScheduler(value) { /***/ }), -/* 234 */ +/* 313 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromArray", function() { return fromArray; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_subscribeToArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(235); -/** PURE_IMPORTS_START _Observable,_Subscription,_util_subscribeToArray PURE_IMPORTS_END */ +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(314); +/* harmony import */ var _scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); +/** PURE_IMPORTS_START _Observable,_util_subscribeToArray,_scheduled_scheduleArray PURE_IMPORTS_END */ function fromArray(input, scheduler) { if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToArray__WEBPACK_IMPORTED_MODULE_2__["subscribeToArray"])(input)); + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__["subscribeToArray"])(input)); } else { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - var i = 0; - sub.add(scheduler.schedule(function () { - if (i === input.length) { - subscriber.complete(); - return; - } - subscriber.next(input[i++]); - if (!subscriber.closed) { - sub.add(this.schedule()); - } - })); - return sub; - }); + return Object(_scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(input, scheduler); } } //# sourceMappingURL=fromArray.js.map /***/ }), -/* 235 */ +/* 314 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29134,44 +35970,52 @@ var subscribeToArray = function (array) { for (var i = 0, len = array.length; i < len && !subscriber.closed; i++) { subscriber.next(array[i]); } - if (!subscriber.closed) { - subscriber.complete(); - } + subscriber.complete(); }; }; //# sourceMappingURL=subscribeToArray.js.map /***/ }), -/* 236 */ +/* 315 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scalar", function() { return scalar; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleArray", function() { return scheduleArray; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ -function scalar(value) { - var result = new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - subscriber.next(value); - subscriber.complete(); + +function scheduleArray(input, scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + var i = 0; + sub.add(scheduler.schedule(function () { + if (i === input.length) { + subscriber.complete(); + return; + } + subscriber.next(input[i++]); + if (!subscriber.closed) { + sub.add(this.schedule()); + } + })); + return sub; }); - result._isScalar = true; - result.value = value; - return result; } -//# sourceMappingURL=scalar.js.map +//# sourceMappingURL=scheduleArray.js.map /***/ }), -/* 237 */ +/* 316 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return throwError; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function throwError(error, scheduler) { @@ -29190,15 +36034,15 @@ function dispatch(_a) { /***/ }), -/* 238 */ +/* 317 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return AsyncSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subject,_Subscription PURE_IMPORTS_END */ @@ -29249,14 +36093,14 @@ var AsyncSubject = /*@__PURE__*/ (function (_super) { /***/ }), -/* 239 */ +/* 318 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "asap", function() { return asap; }); -/* harmony import */ var _AsapAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(240); -/* harmony import */ var _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(242); +/* harmony import */ var _AsapAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(319); +/* harmony import */ var _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(321); /** PURE_IMPORTS_START _AsapAction,_AsapScheduler PURE_IMPORTS_END */ @@ -29265,15 +36109,15 @@ var asap = /*@__PURE__*/ new _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__["AsapSc /***/ }), -/* 240 */ +/* 319 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapAction", function() { return AsapAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_Immediate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(241); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(224); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_Immediate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(303); /** PURE_IMPORTS_START tslib,_util_Immediate,_AsyncAction PURE_IMPORTS_END */ @@ -29316,7 +36160,7 @@ var AsapAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 241 */ +/* 320 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29346,14 +36190,14 @@ var Immediate = { /***/ }), -/* 242 */ +/* 321 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapScheduler", function() { return AsapScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); /** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ @@ -29390,14 +36234,14 @@ var AsapScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 243 */ +/* 322 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "async", function() { return async; }); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(224); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(303); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); /** PURE_IMPORTS_START _AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ @@ -29406,14 +36250,14 @@ var async = /*@__PURE__*/ new _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["Asyn /***/ }), -/* 244 */ +/* 323 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "animationFrame", function() { return animationFrame; }); -/* harmony import */ var _AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(245); -/* harmony import */ var _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); +/* harmony import */ var _AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(324); +/* harmony import */ var _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(325); /** PURE_IMPORTS_START _AnimationFrameAction,_AnimationFrameScheduler PURE_IMPORTS_END */ @@ -29422,14 +36266,14 @@ var animationFrame = /*@__PURE__*/ new _AnimationFrameScheduler__WEBPACK_IMPORTE /***/ }), -/* 245 */ +/* 324 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameAction", function() { return AnimationFrameAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(224); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(303); /** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ @@ -29471,14 +36315,14 @@ var AnimationFrameAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 246 */ +/* 325 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameScheduler", function() { return AnimationFrameScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); /** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ @@ -29515,16 +36359,16 @@ var AnimationFrameScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 247 */ +/* 326 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return VirtualTimeScheduler; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return VirtualAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(224); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(303); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(306); /** PURE_IMPORTS_START tslib,_AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ @@ -29547,7 +36391,9 @@ var VirtualTimeScheduler = /*@__PURE__*/ (function (_super) { VirtualTimeScheduler.prototype.flush = function () { var _a = this, actions = _a.actions, maxFrames = _a.maxFrames; var error, action; - while ((action = actions.shift()) && (this.frame = action.delay) <= maxFrames) { + while ((action = actions[0]) && action.delay <= maxFrames) { + actions.shift(); + this.frame = action.delay; if (error = action.execute(action.state, action.delay)) { break; } @@ -29636,7 +36482,7 @@ var VirtualAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 248 */ +/* 327 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29650,13 +36496,13 @@ function identity(x) { /***/ }), -/* 249 */ +/* 328 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return isObservable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function isObservable(obj) { @@ -29666,90 +36512,86 @@ function isObservable(obj) { /***/ }), -/* 250 */ +/* 329 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return ArgumentOutOfRangeError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ - -var ArgumentOutOfRangeError = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ArgumentOutOfRangeError, _super); - function ArgumentOutOfRangeError() { - var _this = _super.call(this, 'argument out of range') || this; - _this.name = 'ArgumentOutOfRangeError'; - Object.setPrototypeOf(_this, ArgumentOutOfRangeError.prototype); - return _this; +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var ArgumentOutOfRangeErrorImpl = /*@__PURE__*/ (function () { + function ArgumentOutOfRangeErrorImpl() { + Error.call(this); + this.message = 'argument out of range'; + this.name = 'ArgumentOutOfRangeError'; + return this; } - return ArgumentOutOfRangeError; -}(Error)); - + ArgumentOutOfRangeErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return ArgumentOutOfRangeErrorImpl; +})(); +var ArgumentOutOfRangeError = ArgumentOutOfRangeErrorImpl; //# sourceMappingURL=ArgumentOutOfRangeError.js.map /***/ }), -/* 251 */ +/* 330 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return EmptyError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ - -var EmptyError = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](EmptyError, _super); - function EmptyError() { - var _this = _super.call(this, 'no elements in sequence') || this; - _this.name = 'EmptyError'; - Object.setPrototypeOf(_this, EmptyError.prototype); - return _this; +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var EmptyErrorImpl = /*@__PURE__*/ (function () { + function EmptyErrorImpl() { + Error.call(this); + this.message = 'no elements in sequence'; + this.name = 'EmptyError'; + return this; } - return EmptyError; -}(Error)); - + EmptyErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return EmptyErrorImpl; +})(); +var EmptyError = EmptyErrorImpl; //# sourceMappingURL=EmptyError.js.map /***/ }), -/* 252 */ +/* 331 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return TimeoutError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ - -var TimeoutError = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TimeoutError, _super); - function TimeoutError() { - var _this = _super.call(this, 'Timeout has occurred') || this; - _this.name = 'TimeoutError'; - Object.setPrototypeOf(_this, TimeoutError.prototype); - return _this; +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var TimeoutErrorImpl = /*@__PURE__*/ (function () { + function TimeoutErrorImpl() { + Error.call(this); + this.message = 'Timeout has occurred'; + this.name = 'TimeoutError'; + return this; } - return TimeoutError; -}(Error)); - + TimeoutErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return TimeoutErrorImpl; +})(); +var TimeoutError = TimeoutErrorImpl; //# sourceMappingURL=TimeoutError.js.map /***/ }), -/* 253 */ +/* 332 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return bindCallback; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(238); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(254); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(205); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(233); -/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_isArray,_util_isScheduler PURE_IMPORTS_END */ +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(317); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); +/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(277); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(285); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(312); +/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_canReportError,_util_isArray,_util_isScheduler PURE_IMPORTS_END */ + @@ -29757,7 +36599,7 @@ __webpack_require__.r(__webpack_exports__); function bindCallback(callbackFunc, resultSelector, scheduler) { if (resultSelector) { - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_4__["isScheduler"])(resultSelector)) { + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(resultSelector)) { scheduler = resultSelector; } else { @@ -29766,7 +36608,7 @@ function bindCallback(callbackFunc, resultSelector, scheduler) { for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } - return bindCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_3__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + return bindCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_4__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); }; } } @@ -29799,7 +36641,12 @@ function bindCallback(callbackFunc, resultSelector, scheduler) { callbackFunc.apply(context, args.concat([handler])); } catch (err) { - subject.error(err); + if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_3__["canReportError"])(subject)) { + subject.error(err); + } + else { + console.warn(err); + } } } return subject.subscribe(subscriber); @@ -29851,15 +36698,15 @@ function dispatchError(state) { /***/ }), -/* 254 */ +/* 333 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "map", function() { return map; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MapOperator", function() { return MapOperator; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -29908,18 +36755,20 @@ var MapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 255 */ +/* 334 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return bindNodeCallback; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(238); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(254); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(233); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(205); -/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_isScheduler,_util_isArray PURE_IMPORTS_END */ +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(317); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); +/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(277); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(312); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(285); +/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_canReportError,_util_isScheduler,_util_isArray PURE_IMPORTS_END */ + @@ -29927,7 +36776,7 @@ __webpack_require__.r(__webpack_exports__); function bindNodeCallback(callbackFunc, resultSelector, scheduler) { if (resultSelector) { - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(resultSelector)) { + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_4__["isScheduler"])(resultSelector)) { scheduler = resultSelector; } else { @@ -29936,7 +36785,7 @@ function bindNodeCallback(callbackFunc, resultSelector, scheduler) { for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } - return bindNodeCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_4__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + return bindNodeCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_5__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); }; } } @@ -29975,7 +36824,12 @@ function bindNodeCallback(callbackFunc, resultSelector, scheduler) { callbackFunc.apply(context, args.concat([handler])); } catch (err) { - subject.error(err); + if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_3__["canReportError"])(subject)) { + subject.error(err); + } + else { + console.warn(err); + } } } return subject.subscribe(subscriber); @@ -30029,7 +36883,7 @@ function dispatchError(arg) { /***/ }), -/* 256 */ +/* 335 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30037,12 +36891,12 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestOperator", function() { return CombineLatestOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestSubscriber", function() { return CombineLatestSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(233); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(234); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(313); /** PURE_IMPORTS_START tslib,_util_isScheduler,_util_isArray,_OuterSubscriber,_util_subscribeToResult,_fromArray PURE_IMPORTS_END */ @@ -30147,14 +37001,14 @@ var CombineLatestSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 257 */ +/* 336 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OuterSubscriber", function() { return OuterSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -30179,33 +37033,43 @@ var OuterSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 258 */ +/* 337 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToResult", function() { return subscribeToResult; }); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(259); -/* harmony import */ var _subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(260); -/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo PURE_IMPORTS_END */ +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(338); +/* harmony import */ var _subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(339); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); +/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo,_Observable PURE_IMPORTS_END */ -function subscribeToResult(outerSubscriber, result, outerValue, outerIndex) { - var destination = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__["InnerSubscriber"](outerSubscriber, outerValue, outerIndex); + +function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) { + if (destination === void 0) { + destination = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__["InnerSubscriber"](outerSubscriber, outerValue, outerIndex); + } + if (destination.closed) { + return undefined; + } + if (result instanceof _Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"]) { + return result.subscribe(destination); + } return Object(_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(result)(destination); } //# sourceMappingURL=subscribeToResult.js.map /***/ }), -/* 259 */ +/* 338 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InnerSubscriber", function() { return InnerSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -30237,24 +37101,22 @@ var InnerSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 260 */ +/* 339 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeTo", function() { return subscribeTo; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _subscribeToArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(235); -/* harmony import */ var _subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); -/* harmony import */ var _subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(262); -/* harmony import */ var _subscribeToObservable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(264); -/* harmony import */ var _isArrayLike__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(265); -/* harmony import */ var _isPromise__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(266); -/* harmony import */ var _isObject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(206); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(263); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(211); -/** PURE_IMPORTS_START _Observable,_subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */ - +/* harmony import */ var _subscribeToArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(314); +/* harmony import */ var _subscribeToPromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(340); +/* harmony import */ var _subscribeToIterable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(341); +/* harmony import */ var _subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(343); +/* harmony import */ var _isArrayLike__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(344); +/* harmony import */ var _isPromise__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(345); +/* harmony import */ var _isObject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(286); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(342); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(290); +/** PURE_IMPORTS_START _subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */ @@ -30265,32 +37127,20 @@ __webpack_require__.r(__webpack_exports__); var subscribeTo = function (result) { - if (result instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { - return function (subscriber) { - if (result._isScalar) { - subscriber.next(result.value); - subscriber.complete(); - return undefined; - } - else { - return result.subscribe(subscriber); - } - }; + if (!!result && typeof result[_symbol_observable__WEBPACK_IMPORTED_MODULE_8__["observable"]] === 'function') { + return Object(_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__["subscribeToObservable"])(result); } - else if (result && typeof result[_symbol_observable__WEBPACK_IMPORTED_MODULE_9__["observable"]] === 'function') { - return Object(_subscribeToObservable__WEBPACK_IMPORTED_MODULE_4__["subscribeToObservable"])(result); + else if (Object(_isArrayLike__WEBPACK_IMPORTED_MODULE_4__["isArrayLike"])(result)) { + return Object(_subscribeToArray__WEBPACK_IMPORTED_MODULE_0__["subscribeToArray"])(result); } - else if (Object(_isArrayLike__WEBPACK_IMPORTED_MODULE_5__["isArrayLike"])(result)) { - return Object(_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__["subscribeToArray"])(result); + else if (Object(_isPromise__WEBPACK_IMPORTED_MODULE_5__["isPromise"])(result)) { + return Object(_subscribeToPromise__WEBPACK_IMPORTED_MODULE_1__["subscribeToPromise"])(result); } - else if (Object(_isPromise__WEBPACK_IMPORTED_MODULE_6__["isPromise"])(result)) { - return Object(_subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__["subscribeToPromise"])(result); - } - else if (result && typeof result[_symbol_iterator__WEBPACK_IMPORTED_MODULE_8__["iterator"]] === 'function') { - return Object(_subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__["subscribeToIterable"])(result); + else if (!!result && typeof result[_symbol_iterator__WEBPACK_IMPORTED_MODULE_7__["iterator"]] === 'function') { + return Object(_subscribeToIterable__WEBPACK_IMPORTED_MODULE_2__["subscribeToIterable"])(result); } else { - var value = Object(_isObject__WEBPACK_IMPORTED_MODULE_7__["isObject"])(result) ? 'an invalid object' : "'" + result + "'"; + var value = Object(_isObject__WEBPACK_IMPORTED_MODULE_6__["isObject"])(result) ? 'an invalid object' : "'" + result + "'"; var msg = "You provided " + value + " where a stream was expected." + ' You can provide an Observable, Promise, Array, or Iterable.'; throw new TypeError(msg); @@ -30300,13 +37150,13 @@ var subscribeTo = function (result) { /***/ }), -/* 261 */ +/* 340 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToPromise", function() { return subscribeToPromise; }); -/* harmony import */ var _hostReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(203); +/* harmony import */ var _hostReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(283); /** PURE_IMPORTS_START _hostReportError PURE_IMPORTS_END */ var subscribeToPromise = function (promise) { @@ -30325,13 +37175,13 @@ var subscribeToPromise = function (promise) { /***/ }), -/* 262 */ +/* 341 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToIterable", function() { return subscribeToIterable; }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(263); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(342); /** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ var subscribeToIterable = function (iterable) { @@ -30362,7 +37212,7 @@ var subscribeToIterable = function (iterable) { /***/ }), -/* 263 */ +/* 342 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30383,13 +37233,13 @@ var $$iterator = iterator; /***/ }), -/* 264 */ +/* 343 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToObservable", function() { return subscribeToObservable; }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(211); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(290); /** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ var subscribeToObservable = function (obj) { @@ -30407,7 +37257,7 @@ var subscribeToObservable = function (obj) { /***/ }), -/* 265 */ +/* 344 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30419,7 +37269,7 @@ var isArrayLike = (function (x) { return x && typeof x.length === 'number' && ty /***/ }), -/* 266 */ +/* 345 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30427,25 +37277,21 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isPromise", function() { return isPromise; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ function isPromise(value) { - return value && typeof value.subscribe !== 'function' && typeof value.then === 'function'; + return !!value && typeof value.subscribe !== 'function' && typeof value.then === 'function'; } //# sourceMappingURL=isPromise.js.map /***/ }), -/* 267 */ +/* 346 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(233); -/* harmony import */ var _of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(268); -/* harmony import */ var _operators_concatAll__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(274); -/** PURE_IMPORTS_START _util_isScheduler,_of,_from,_operators_concatAll PURE_IMPORTS_END */ - - +/* harmony import */ var _of__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(311); +/* harmony import */ var _operators_concatAll__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(347); +/** PURE_IMPORTS_START _of,_operators_concatAll PURE_IMPORTS_END */ function concat() { @@ -30453,249 +37299,19 @@ function concat() { for (var _i = 0; _i < arguments.length; _i++) { observables[_i] = arguments[_i]; } - if (observables.length === 1 || (observables.length === 2 && Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_0__["isScheduler"])(observables[1]))) { - return Object(_from__WEBPACK_IMPORTED_MODULE_2__["from"])(observables[0]); - } - return Object(_operators_concatAll__WEBPACK_IMPORTED_MODULE_3__["concatAll"])()(_of__WEBPACK_IMPORTED_MODULE_1__["of"].apply(void 0, observables)); + return Object(_operators_concatAll__WEBPACK_IMPORTED_MODULE_1__["concatAll"])()(_of__WEBPACK_IMPORTED_MODULE_0__["of"].apply(void 0, observables)); } //# sourceMappingURL=concat.js.map /***/ }), -/* 268 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "from", function() { return from; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(266); -/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(265); -/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(269); -/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(270); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(234); -/* harmony import */ var _fromPromise__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(271); -/* harmony import */ var _fromIterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(272); -/* harmony import */ var _fromObservable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(273); -/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(260); -/** PURE_IMPORTS_START _Observable,_util_isPromise,_util_isArrayLike,_util_isInteropObservable,_util_isIterable,_fromArray,_fromPromise,_fromIterable,_fromObservable,_util_subscribeTo PURE_IMPORTS_END */ - - - - - - - - - - -function from(input, scheduler) { - if (!scheduler) { - if (input instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { - return input; - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_9__["subscribeTo"])(input)); - } - if (input != null) { - if (Object(_util_isInteropObservable__WEBPACK_IMPORTED_MODULE_3__["isInteropObservable"])(input)) { - return Object(_fromObservable__WEBPACK_IMPORTED_MODULE_8__["fromObservable"])(input, scheduler); - } - else if (Object(_util_isPromise__WEBPACK_IMPORTED_MODULE_1__["isPromise"])(input)) { - return Object(_fromPromise__WEBPACK_IMPORTED_MODULE_6__["fromPromise"])(input, scheduler); - } - else if (Object(_util_isArrayLike__WEBPACK_IMPORTED_MODULE_2__["isArrayLike"])(input)) { - return Object(_fromArray__WEBPACK_IMPORTED_MODULE_5__["fromArray"])(input, scheduler); - } - else if (Object(_util_isIterable__WEBPACK_IMPORTED_MODULE_4__["isIterable"])(input) || typeof input === 'string') { - return Object(_fromIterable__WEBPACK_IMPORTED_MODULE_7__["fromIterable"])(input, scheduler); - } - } - throw new TypeError((input !== null && typeof input || input) + ' is not observable'); -} -//# sourceMappingURL=from.js.map - - -/***/ }), -/* 269 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isInteropObservable", function() { return isInteropObservable; }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(211); -/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ - -function isInteropObservable(input) { - return input && typeof input[_symbol_observable__WEBPACK_IMPORTED_MODULE_0__["observable"]] === 'function'; -} -//# sourceMappingURL=isInteropObservable.js.map - - -/***/ }), -/* 270 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isIterable", function() { return isIterable; }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(263); -/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ - -function isIterable(input) { - return input && typeof input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_0__["iterator"]] === 'function'; -} -//# sourceMappingURL=isIterable.js.map - - -/***/ }), -/* 271 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromPromise", function() { return fromPromise; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); -/** PURE_IMPORTS_START _Observable,_Subscription,_util_subscribeToPromise PURE_IMPORTS_END */ - - - -function fromPromise(input, scheduler) { - if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__["subscribeToPromise"])(input)); - } - else { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - sub.add(scheduler.schedule(function () { - return input.then(function (value) { - sub.add(scheduler.schedule(function () { - subscriber.next(value); - sub.add(scheduler.schedule(function () { return subscriber.complete(); })); - })); - }, function (err) { - sub.add(scheduler.schedule(function () { return subscriber.error(err); })); - }); - })); - return sub; - }); - } -} -//# sourceMappingURL=fromPromise.js.map - - -/***/ }), -/* 272 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromIterable", function() { return fromIterable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(263); -/* harmony import */ var _util_subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(262); -/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator,_util_subscribeToIterable PURE_IMPORTS_END */ - - - - -function fromIterable(input, scheduler) { - if (!input) { - throw new Error('Iterable cannot be null'); - } - if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__["subscribeToIterable"])(input)); - } - else { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - var iterator; - sub.add(function () { - if (iterator && typeof iterator.return === 'function') { - iterator.return(); - } - }); - sub.add(scheduler.schedule(function () { - iterator = input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_2__["iterator"]](); - sub.add(scheduler.schedule(function () { - if (subscriber.closed) { - return; - } - var value; - var done; - try { - var result = iterator.next(); - value = result.value; - done = result.done; - } - catch (err) { - subscriber.error(err); - return; - } - if (done) { - subscriber.complete(); - } - else { - subscriber.next(value); - this.schedule(); - } - })); - })); - return sub; - }); - } -} -//# sourceMappingURL=fromIterable.js.map - - -/***/ }), -/* 273 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromObservable", function() { return fromObservable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(211); -/* harmony import */ var _util_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(264); -/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable,_util_subscribeToObservable PURE_IMPORTS_END */ - - - - -function fromObservable(input, scheduler) { - if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__["subscribeToObservable"])(input)); - } - else { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - sub.add(scheduler.schedule(function () { - var observable = input[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]](); - sub.add(observable.subscribe({ - next: function (value) { sub.add(scheduler.schedule(function () { return subscriber.next(value); })); }, - error: function (err) { sub.add(scheduler.schedule(function () { return subscriber.error(err); })); }, - complete: function () { sub.add(scheduler.schedule(function () { return subscriber.complete(); })); }, - })); - })); - return sub; - }); - } -} -//# sourceMappingURL=fromObservable.js.map - - -/***/ }), -/* 274 */ +/* 347 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return concatAll; }); -/* harmony import */ var _mergeAll__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/* harmony import */ var _mergeAll__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(348); /** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */ function concatAll() { @@ -30705,14 +37321,14 @@ function concatAll() { /***/ }), -/* 275 */ +/* 348 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return mergeAll; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(248); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(349); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(327); /** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */ @@ -30726,7 +37342,7 @@ function mergeAll(concurrent) { /***/ }), -/* 276 */ +/* 349 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30734,12 +37350,14 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return mergeMap; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapOperator", function() { return MergeMapOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapSubscriber", function() { return MergeMapSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(257); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(268); -/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_map,_observable_from PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(338); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(350); +/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber,_map,_observable_from PURE_IMPORTS_END */ + @@ -30750,7 +37368,7 @@ function mergeMap(project, resultSelector, concurrent) { concurrent = Number.POSITIVE_INFINITY; } if (typeof resultSelector === 'function') { - return function (source) { return source.pipe(mergeMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_4__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); }; + return function (source) { return source.pipe(mergeMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); }; } else if (typeof resultSelector === 'number') { concurrent = resultSelector; @@ -30808,13 +37426,17 @@ var MergeMapSubscriber = /*@__PURE__*/ (function (_super) { this._innerSub(result, value, index); }; MergeMapSubscriber.prototype._innerSub = function (ish, value, index) { - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index)); + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber); }; MergeMapSubscriber.prototype._complete = function () { this.hasCompleted = true; if (this.active === 0 && this.buffer.length === 0) { this.destination.complete(); } + this.unsubscribe(); }; MergeMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { this.destination.next(innerValue); @@ -30837,15 +37459,239 @@ var MergeMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 277 */ +/* 350 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "from", function() { return from; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(339); +/* harmony import */ var _scheduled_scheduled__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(351); +/** PURE_IMPORTS_START _Observable,_util_subscribeTo,_scheduled_scheduled PURE_IMPORTS_END */ + + + +function from(input, scheduler) { + if (!scheduler) { + if (input instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { + return input; + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(input)); + } + else { + return Object(_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_2__["scheduled"])(input, scheduler); + } +} +//# sourceMappingURL=from.js.map + + +/***/ }), +/* 351 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduled", function() { return scheduled; }); +/* harmony import */ var _scheduleObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(352); +/* harmony import */ var _schedulePromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(353); +/* harmony import */ var _scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); +/* harmony import */ var _scheduleIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(354); +/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(355); +/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(345); +/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(344); +/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(356); +/** PURE_IMPORTS_START _scheduleObservable,_schedulePromise,_scheduleArray,_scheduleIterable,_util_isInteropObservable,_util_isPromise,_util_isArrayLike,_util_isIterable PURE_IMPORTS_END */ + + + + + + + + +function scheduled(input, scheduler) { + if (input != null) { + if (Object(_util_isInteropObservable__WEBPACK_IMPORTED_MODULE_4__["isInteropObservable"])(input)) { + return Object(_scheduleObservable__WEBPACK_IMPORTED_MODULE_0__["scheduleObservable"])(input, scheduler); + } + else if (Object(_util_isPromise__WEBPACK_IMPORTED_MODULE_5__["isPromise"])(input)) { + return Object(_schedulePromise__WEBPACK_IMPORTED_MODULE_1__["schedulePromise"])(input, scheduler); + } + else if (Object(_util_isArrayLike__WEBPACK_IMPORTED_MODULE_6__["isArrayLike"])(input)) { + return Object(_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(input, scheduler); + } + else if (Object(_util_isIterable__WEBPACK_IMPORTED_MODULE_7__["isIterable"])(input) || typeof input === 'string') { + return Object(_scheduleIterable__WEBPACK_IMPORTED_MODULE_3__["scheduleIterable"])(input, scheduler); + } + } + throw new TypeError((input !== null && typeof input || input) + ' is not observable'); +} +//# sourceMappingURL=scheduled.js.map + + +/***/ }), +/* 352 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleObservable", function() { return scheduleObservable; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(290); +/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable PURE_IMPORTS_END */ + + + +function scheduleObservable(input, scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + sub.add(scheduler.schedule(function () { + var observable = input[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]](); + sub.add(observable.subscribe({ + next: function (value) { sub.add(scheduler.schedule(function () { return subscriber.next(value); })); }, + error: function (err) { sub.add(scheduler.schedule(function () { return subscriber.error(err); })); }, + complete: function () { sub.add(scheduler.schedule(function () { return subscriber.complete(); })); }, + })); + })); + return sub; + }); +} +//# sourceMappingURL=scheduleObservable.js.map + + +/***/ }), +/* 353 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "schedulePromise", function() { return schedulePromise; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ + + +function schedulePromise(input, scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + sub.add(scheduler.schedule(function () { + return input.then(function (value) { + sub.add(scheduler.schedule(function () { + subscriber.next(value); + sub.add(scheduler.schedule(function () { return subscriber.complete(); })); + })); + }, function (err) { + sub.add(scheduler.schedule(function () { return subscriber.error(err); })); + }); + })); + return sub; + }); +} +//# sourceMappingURL=schedulePromise.js.map + + +/***/ }), +/* 354 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleIterable", function() { return scheduleIterable; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(342); +/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator PURE_IMPORTS_END */ + + + +function scheduleIterable(input, scheduler) { + if (!input) { + throw new Error('Iterable cannot be null'); + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + var iterator; + sub.add(function () { + if (iterator && typeof iterator.return === 'function') { + iterator.return(); + } + }); + sub.add(scheduler.schedule(function () { + iterator = input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_2__["iterator"]](); + sub.add(scheduler.schedule(function () { + if (subscriber.closed) { + return; + } + var value; + var done; + try { + var result = iterator.next(); + value = result.value; + done = result.done; + } + catch (err) { + subscriber.error(err); + return; + } + if (done) { + subscriber.complete(); + } + else { + subscriber.next(value); + this.schedule(); + } + })); + })); + return sub; + }); +} +//# sourceMappingURL=scheduleIterable.js.map + + +/***/ }), +/* 355 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isInteropObservable", function() { return isInteropObservable; }); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(290); +/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ + +function isInteropObservable(input) { + return input && typeof input[_symbol_observable__WEBPACK_IMPORTED_MODULE_0__["observable"]] === 'function'; +} +//# sourceMappingURL=isInteropObservable.js.map + + +/***/ }), +/* 356 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isIterable", function() { return isIterable; }); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(342); +/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ + +function isIterable(input) { + return input && typeof input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_0__["iterator"]] === 'function'; +} +//# sourceMappingURL=isIterable.js.map + + +/***/ }), +/* 357 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return defer; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); /** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ @@ -30868,22 +37714,18 @@ function defer(observableFactory) { /***/ }), -/* 278 */ +/* 358 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return forkJoin; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(196); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(257); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(254); -/** PURE_IMPORTS_START tslib,_Observable,_util_isArray,_empty,_util_subscribeToResult,_OuterSubscriber,_operators_map PURE_IMPORTS_END */ - - +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); +/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(286); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(350); +/** PURE_IMPORTS_START _Observable,_util_isArray,_operators_map,_util_isObject,_from PURE_IMPORTS_END */ @@ -30894,86 +37736,83 @@ function forkJoin() { for (var _i = 0; _i < arguments.length; _i++) { sources[_i] = arguments[_i]; } - var resultSelector; - if (typeof sources[sources.length - 1] === 'function') { - resultSelector = sources.pop(); - } - if (sources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(sources[0])) { - sources = sources[0]; - } - if (sources.length === 0) { - return _empty__WEBPACK_IMPORTED_MODULE_3__["EMPTY"]; + if (sources.length === 1) { + var first_1 = sources[0]; + if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(first_1)) { + return forkJoinInternal(first_1, null); + } + if (Object(_util_isObject__WEBPACK_IMPORTED_MODULE_3__["isObject"])(first_1) && Object.getPrototypeOf(first_1) === Object.prototype) { + var keys = Object.keys(first_1); + return forkJoinInternal(keys.map(function (key) { return first_1[key]; }), keys); + } } - if (resultSelector) { - return forkJoin(sources).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_6__["map"])(function (args) { return resultSelector.apply(void 0, args); })); + if (typeof sources[sources.length - 1] === 'function') { + var resultSelector_1 = sources.pop(); + sources = (sources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(sources[0])) ? sources[0] : sources; + return forkJoinInternal(sources, null).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return resultSelector_1.apply(void 0, args); })); } - return new _Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"](function (subscriber) { - return new ForkJoinSubscriber(subscriber, sources); - }); + return forkJoinInternal(sources, null); } -var ForkJoinSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ForkJoinSubscriber, _super); - function ForkJoinSubscriber(destination, sources) { - var _this = _super.call(this, destination) || this; - _this.sources = sources; - _this.completed = 0; - _this.haveValues = 0; +function forkJoinInternal(sources, keys) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { var len = sources.length; - _this.values = new Array(len); - for (var i = 0; i < len; i++) { - var source = sources[i]; - var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(_this, source, null, i); - if (innerSubscription) { - _this.add(innerSubscription); - } - } - return _this; - } - ForkJoinSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.values[outerIndex] = innerValue; - if (!innerSub._hasValue) { - innerSub._hasValue = true; - this.haveValues++; - } - }; - ForkJoinSubscriber.prototype.notifyComplete = function (innerSub) { - var _a = this, destination = _a.destination, haveValues = _a.haveValues, values = _a.values; - var len = values.length; - if (!innerSub._hasValue) { - destination.complete(); - return; - } - this.completed++; - if (this.completed !== len) { + if (len === 0) { + subscriber.complete(); return; } - if (haveValues === len) { - destination.next(values); + var values = new Array(len); + var completed = 0; + var emitted = 0; + var _loop_1 = function (i) { + var source = Object(_from__WEBPACK_IMPORTED_MODULE_4__["from"])(sources[i]); + var hasValue = false; + subscriber.add(source.subscribe({ + next: function (value) { + if (!hasValue) { + hasValue = true; + emitted++; + } + values[i] = value; + }, + error: function (err) { return subscriber.error(err); }, + complete: function () { + completed++; + if (completed === len || !hasValue) { + if (emitted === len) { + subscriber.next(keys ? + keys.reduce(function (result, key, i) { return (result[key] = values[i], result); }, {}) : + values); + } + subscriber.complete(); + } + } + })); + }; + for (var i = 0; i < len; i++) { + _loop_1(i); } - destination.complete(); - }; - return ForkJoinSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__["OuterSubscriber"])); + }); +} //# sourceMappingURL=forkJoin.js.map /***/ }), -/* 279 */ +/* 359 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return fromEvent; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333); /** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ -var toString = Object.prototype.toString; +var toString = /*@__PURE__*/ (function () { return Object.prototype.toString; })(); function fromEvent(target, eventName, options, resultSelector) { if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(options)) { resultSelector = options; @@ -31034,16 +37873,16 @@ function isEventTarget(sourceObj) { /***/ }), -/* 280 */ +/* 360 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return fromEventPattern; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333); /** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ @@ -31079,15 +37918,15 @@ function fromEventPattern(addHandler, removeHandler, resultSelector) { /***/ }), -/* 281 */ +/* 361 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return generate; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(248); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(233); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(327); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(312); /** PURE_IMPORTS_START _Observable,_util_identity,_util_isScheduler PURE_IMPORTS_END */ @@ -31216,14 +38055,14 @@ function dispatch(state) { /***/ }), -/* 282 */ +/* 362 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return iif; }); -/* harmony import */ var _defer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(277); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(231); +/* harmony import */ var _defer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(357); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(310); /** PURE_IMPORTS_START _defer,_empty PURE_IMPORTS_END */ @@ -31240,15 +38079,15 @@ function iif(condition, trueResult, falseResult) { /***/ }), -/* 283 */ +/* 363 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return interval; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(364); /** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric PURE_IMPORTS_END */ @@ -31280,13 +38119,13 @@ function dispatch(state) { /***/ }), -/* 284 */ +/* 364 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isNumeric", function() { return isNumeric; }); -/* harmony import */ var _isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); +/* harmony import */ var _isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); /** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */ function isNumeric(val) { @@ -31296,16 +38135,16 @@ function isNumeric(val) { /***/ }), -/* 285 */ +/* 365 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(233); -/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(275); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(234); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); +/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(348); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(313); /** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */ @@ -31337,15 +38176,15 @@ function merge() { /***/ }), -/* 286 */ +/* 366 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return NEVER; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "never", function() { return never; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(213); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(292); /** PURE_IMPORTS_START _Observable,_util_noop PURE_IMPORTS_END */ @@ -31357,16 +38196,16 @@ function never() { /***/ }), -/* 287 */ +/* 367 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(310); /** PURE_IMPORTS_START _Observable,_from,_util_isArray,_empty PURE_IMPORTS_END */ @@ -31397,15 +38236,15 @@ function onErrorResumeNext() { /***/ }), -/* 288 */ +/* 368 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return pairs; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); /** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ @@ -31448,7 +38287,105 @@ function dispatch(state) { /***/ }), -/* 289 */ +/* 369 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); +/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(370); +/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(339); +/* harmony import */ var _operators_filter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(371); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(276); +/** PURE_IMPORTS_START _util_not,_util_subscribeTo,_operators_filter,_Observable PURE_IMPORTS_END */ + + + + +function partition(source, predicate, thisArg) { + return [ + Object(_operators_filter__WEBPACK_IMPORTED_MODULE_2__["filter"])(predicate, thisArg)(new _Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(source))), + Object(_operators_filter__WEBPACK_IMPORTED_MODULE_2__["filter"])(Object(_util_not__WEBPACK_IMPORTED_MODULE_0__["not"])(predicate, thisArg))(new _Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(source))) + ]; +} +//# sourceMappingURL=partition.js.map + + +/***/ }), +/* 370 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "not", function() { return not; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function not(pred, thisArg) { + function notPred() { + return !(notPred.pred.apply(notPred.thisArg, arguments)); + } + notPred.pred = pred; + notPred.thisArg = thisArg; + return notPred; +} +//# sourceMappingURL=not.js.map + + +/***/ }), +/* 371 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return filter; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function filter(predicate, thisArg) { + return function filterOperatorFunction(source) { + return source.lift(new FilterOperator(predicate, thisArg)); + }; +} +var FilterOperator = /*@__PURE__*/ (function () { + function FilterOperator(predicate, thisArg) { + this.predicate = predicate; + this.thisArg = thisArg; + } + FilterOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new FilterSubscriber(subscriber, this.predicate, this.thisArg)); + }; + return FilterOperator; +}()); +var FilterSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FilterSubscriber, _super); + function FilterSubscriber(destination, predicate, thisArg) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.thisArg = thisArg; + _this.count = 0; + return _this; + } + FilterSubscriber.prototype._next = function (value) { + var result; + try { + result = this.predicate.call(this.thisArg, value, this.count++); + } + catch (err) { + this.destination.error(err); + return; + } + if (result) { + this.destination.next(value); + } + }; + return FilterSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=filter.js.map + + +/***/ }), +/* 372 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -31456,11 +38393,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceOperator", function() { return RaceOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceSubscriber", function() { return RaceSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(234); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_util_isArray,_fromArray,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -31542,24 +38479,25 @@ var RaceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 290 */ +/* 373 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "range", function() { return range; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function range(start, count, scheduler) { if (start === void 0) { start = 0; } - if (count === void 0) { - count = 0; - } return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + if (count === undefined) { + count = start; + start = 0; + } var index = 0; var current = start; if (scheduler) { @@ -31600,16 +38538,16 @@ function dispatch(state) { /***/ }), -/* 291 */ +/* 374 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return timer; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(233); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(364); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(312); /** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ @@ -31654,15 +38592,15 @@ function dispatch(state) { /***/ }), -/* 292 */ +/* 375 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "using", function() { return using; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); /** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ @@ -31699,7 +38637,7 @@ function using(resourceFactory, observableFactory) { /***/ }), -/* 293 */ +/* 376 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -31707,13 +38645,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipOperator", function() { return ZipOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipSubscriber", function() { return ZipSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(234); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); -/* harmony import */ var _internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(263); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(313); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(337); +/* harmony import */ var _internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(342); /** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_OuterSubscriber,_util_subscribeToResult,_.._internal_symbol_iterator PURE_IMPORTS_END */ @@ -31771,6 +38709,7 @@ var ZipSubscriber = /*@__PURE__*/ (function (_super) { ZipSubscriber.prototype._complete = function () { var iterators = this.iterators; var len = iterators.length; + this.unsubscribe(); if (len === 0) { this.destination.complete(); return; @@ -31779,7 +38718,8 @@ var ZipSubscriber = /*@__PURE__*/ (function (_super) { for (var i = 0; i < len; i++) { var iterator = iterators[i]; if (iterator.stillUnsubscribed) { - this.add(iterator.subscribe(iterator, i)); + var destination = this.destination; + destination.add(iterator.subscribe(iterator, i)); } else { this.active--; @@ -31933,320 +38873,320 @@ var ZipBufferIterator = /*@__PURE__*/ (function (_super) { /***/ }), -/* 294 */ +/* 377 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(295); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(378); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(296); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(379); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(297); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(380); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(298); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(381); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(299); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(382); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(300); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(383); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(301); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(384); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(302); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(385); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(303); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(386); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(304); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(387); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(305); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(388); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); -/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(274); +/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(347); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(306); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(389); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(307); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(390); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(308); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(391); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(309); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(392); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(310); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(393); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(311); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(394); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(312); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(395); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(314); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(397); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(315); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(398); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(316); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(399); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(317); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(400); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(318); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(401); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(319); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(402); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(324); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(405); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(325); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(406); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(326); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(407); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(327); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(408); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(328); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(409); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); -/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(320); +/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(371); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(329); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(410); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(330); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(411); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(331); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(412); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(332); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(413); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(219); +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(298); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(333); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(414); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(334); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(415); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(335); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(416); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); -/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(254); +/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(333); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(337); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(418); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(338); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(419); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(339); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(420); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(342); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(423); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); -/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(275); +/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(348); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__["mergeAll"]; }); -/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(276); +/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(349); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(343); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(424); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(344); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(425); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(345); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(426); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(346); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(427); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); -/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(229); +/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(308); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(347); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(428); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(348); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(429); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(349); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(430); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(351); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(431); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(352); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(353); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(433); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(354); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(434); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(355); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(435); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(356); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(436); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(340); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(421); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(357); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(437); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(358); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(438); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(359); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(439); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(360); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(440); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); -/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(218); +/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(297); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(361); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(441); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(362); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(442); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(341); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(422); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(363); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(443); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(364); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(444); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(365); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(445); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(366); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(446); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(367); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(447); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(368); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(448); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(369); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(449); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(370); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(450); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(371); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(451); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(372); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(452); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(374); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(454); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(375); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(455); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(376); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(456); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(323); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(404); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(336); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(417); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(377); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(457); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(378); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(458); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(322); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(459); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(379); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(460); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(380); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(461); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(321); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(403); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(381); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(462); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(382); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(463); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(383); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(464); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(384); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(465); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(385); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(466); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(386); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(467); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(387); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(468); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(388); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(469); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(389); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(470); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(390); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(471); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(391); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(472); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(392); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(473); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(393); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(474); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -32358,20 +39298,16 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 295 */ +/* 378 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return audit; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -32401,18 +39337,20 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { this.value = value; this.hasValue = true; if (!this.throttled) { - var duration = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_1__["tryCatch"])(this.durationSelector)(value); - if (duration === _util_errorObject__WEBPACK_IMPORTED_MODULE_2__["errorObject"]) { - this.destination.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_2__["errorObject"].e); + var duration = void 0; + try { + var durationSelector = this.durationSelector; + duration = durationSelector(value); + } + catch (err) { + return this.destination.error(err); + } + var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration); + if (!innerSubscription || innerSubscription.closed) { + this.clearThrottle(); } else { - var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, duration); - if (!innerSubscription || innerSubscription.closed) { - this.clearThrottle(); - } - else { - this.add(this.throttled = innerSubscription); - } + this.add(this.throttled = innerSubscription); } } }; @@ -32436,20 +39374,20 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { this.clearThrottle(); }; return AuditSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); //# sourceMappingURL=audit.js.map /***/ }), -/* 296 */ +/* 379 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); -/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(291); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(378); +/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(374); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -32464,15 +39402,15 @@ function auditTime(duration, scheduler) { /***/ }), -/* 297 */ +/* 380 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return buffer; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -32513,14 +39451,14 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 298 */ +/* 381 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return bufferCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -32614,16 +39552,16 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 299 */ +/* 382 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return bufferTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(198); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(233); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(312); /** PURE_IMPORTS_START tslib,_scheduler_async,_Subscriber,_util_isScheduler PURE_IMPORTS_END */ @@ -32775,16 +39713,16 @@ function dispatchBufferClose(arg) { /***/ }), -/* 300 */ +/* 383 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return bufferToggle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); /** PURE_IMPORTS_START tslib,_Subscription,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ @@ -32895,21 +39833,17 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 301 */ +/* 384 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return bufferWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_Subscription,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_Subscription,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -32973,35 +39907,39 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { this.destination.next(buffer); } this.buffer = []; - var closingNotifier = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(this.closingSelector)(); - if (closingNotifier === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { - this.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"].e); + var closingNotifier; + try { + var closingSelector = this.closingSelector; + closingNotifier = closingSelector(); } - else { - closingSubscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - this.closingSubscription = closingSubscription; - this.add(closingSubscription); - this.subscribing = true; - closingSubscription.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, closingNotifier)); - this.subscribing = false; + catch (err) { + return this.error(err); } + closingSubscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + this.closingSubscription = closingSubscription; + this.add(closingSubscription); + this.subscribing = true; + closingSubscription.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, closingNotifier)); + this.subscribing = false; }; return BufferWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); //# sourceMappingURL=bufferWhen.js.map /***/ }), -/* 302 */ +/* 385 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return catchError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + @@ -33040,7 +39978,9 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { return; } this._unsubscribeAndRecycle(); - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, result)); + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); + this.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber); } }; return CatchSubscriber; @@ -33049,13 +39989,13 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 303 */ +/* 386 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return combineAll; }); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(256); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(335); /** PURE_IMPORTS_START _observable_combineLatest PURE_IMPORTS_END */ function combineAll(project) { @@ -33065,15 +40005,15 @@ function combineAll(project) { /***/ }), -/* 304 */ +/* 387 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(256); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(268); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(335); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(350); /** PURE_IMPORTS_START _util_isArray,_observable_combineLatest,_observable_from PURE_IMPORTS_END */ @@ -33097,13 +40037,13 @@ function combineLatest() { /***/ }), -/* 305 */ +/* 388 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(267); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); /** PURE_IMPORTS_START _observable_concat PURE_IMPORTS_END */ function concat() { @@ -33117,13 +40057,13 @@ function concat() { /***/ }), -/* 306 */ +/* 389 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return concatMap; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(349); /** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ function concatMap(project, resultSelector) { @@ -33133,13 +40073,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 307 */ +/* 390 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(306); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(389); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -33149,14 +40089,14 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 308 */ +/* 391 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "count", function() { return count; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33214,15 +40154,15 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 309 */ +/* 392 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return debounce; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33302,15 +40242,15 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 310 */ +/* 393 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return debounceTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ @@ -33378,14 +40318,14 @@ function dispatchNext(subscriber) { /***/ }), -/* 311 */ +/* 394 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return defaultIfEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33428,17 +40368,17 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 312 */ +/* 395 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(230); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(309); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -33493,7 +40433,8 @@ var DelaySubscriber = /*@__PURE__*/ (function (_super) { }; DelaySubscriber.prototype._schedule = function (scheduler) { this.active = true; - this.add(scheduler.schedule(DelaySubscriber.dispatch, this.delay, { + var destination = this.destination; + destination.add(scheduler.schedule(DelaySubscriber.dispatch, this.delay, { source: this, destination: this.destination, scheduler: scheduler })); }; @@ -33515,9 +40456,11 @@ var DelaySubscriber = /*@__PURE__*/ (function (_super) { this.errored = true; this.queue = []; this.destination.error(err); + this.unsubscribe(); }; DelaySubscriber.prototype._complete = function () { this.scheduleNotification(_Notification__WEBPACK_IMPORTED_MODULE_4__["Notification"].createComplete()); + this.unsubscribe(); }; return DelaySubscriber; }(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); @@ -33532,7 +40475,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 313 */ +/* 396 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -33546,17 +40489,17 @@ function isDate(value) { /***/ }), -/* 314 */ +/* 397 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return delayWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(196); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33588,6 +40531,7 @@ var DelayWhenSubscriber = /*@__PURE__*/ (function (_super) { _this.delayDurationSelector = delayDurationSelector; _this.completed = false; _this.delayNotifierSubscriptions = []; + _this.index = 0; return _this; } DelayWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { @@ -33606,8 +40550,9 @@ var DelayWhenSubscriber = /*@__PURE__*/ (function (_super) { this.tryComplete(); }; DelayWhenSubscriber.prototype._next = function (value) { + var index = this.index++; try { - var delayNotifier = this.delayDurationSelector(value); + var delayNotifier = this.delayDurationSelector(value, index); if (delayNotifier) { this.tryDelay(delayNotifier, value); } @@ -33619,6 +40564,7 @@ var DelayWhenSubscriber = /*@__PURE__*/ (function (_super) { DelayWhenSubscriber.prototype._complete = function () { this.completed = true; this.tryComplete(); + this.unsubscribe(); }; DelayWhenSubscriber.prototype.removeSubscription = function (subscription) { subscription.unsubscribe(); @@ -33631,7 +40577,8 @@ var DelayWhenSubscriber = /*@__PURE__*/ (function (_super) { DelayWhenSubscriber.prototype.tryDelay = function (delayNotifier, value) { var notifierSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, delayNotifier, value); if (notifierSubscription && !notifierSubscription.closed) { - this.add(notifierSubscription); + var destination = this.destination; + destination.add(notifierSubscription); this.delayNotifierSubscriptions.push(notifierSubscription); } }; @@ -33672,6 +40619,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { this.parent.error(err); }; SubscriptionDelaySubscriber.prototype._complete = function () { + this.unsubscribe(); this.subscribeToSource(); }; SubscriptionDelaySubscriber.prototype.subscribeToSource = function () { @@ -33687,14 +40635,14 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 315 */ +/* 398 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return dematerialize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33725,16 +40673,16 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 316 */ +/* 399 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return distinct; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DistinctSubscriber", function() { return DistinctSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33803,19 +40751,15 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 317 */ +/* 400 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return distinctUntilChanged; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_tryCatch,_util_errorObject PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ function distinctUntilChanged(compare, keySelector) { @@ -33846,25 +40790,28 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { return x === y; }; DistinctUntilChangedSubscriber.prototype._next = function (value) { - var keySelector = this.keySelector; - var key = value; - if (keySelector) { - key = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(this.keySelector)(value); - if (key === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { - return this.destination.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"].e); - } + var key; + try { + var keySelector = this.keySelector; + key = keySelector ? keySelector(value) : value; + } + catch (err) { + return this.destination.error(err); } var result = false; if (this.hasKey) { - result = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(this.compare)(this.key, key); - if (result === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { - return this.destination.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"].e); + try { + var compare = this.compare; + result = compare(this.key, key); + } + catch (err) { + return this.destination.error(err); } } else { this.hasKey = true; } - if (Boolean(result) === false) { + if (!result) { this.key = key; this.destination.next(value); } @@ -33875,13 +40822,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 318 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(317); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(400); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -33891,17 +40838,17 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 319 */ +/* 402 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(250); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(321); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(323); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(329); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(403); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(394); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(404); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -33923,190 +40870,82 @@ function elementAt(index, defaultValue) { /***/ }), -/* 320 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return filter; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - - -function filter(predicate, thisArg) { - return function filterOperatorFunction(source) { - return source.lift(new FilterOperator(predicate, thisArg)); - }; -} -var FilterOperator = /*@__PURE__*/ (function () { - function FilterOperator(predicate, thisArg) { - this.predicate = predicate; - this.thisArg = thisArg; - } - FilterOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new FilterSubscriber(subscriber, this.predicate, this.thisArg)); - }; - return FilterOperator; -}()); -var FilterSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FilterSubscriber, _super); - function FilterSubscriber(destination, predicate, thisArg) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.thisArg = thisArg; - _this.count = 0; - return _this; - } - FilterSubscriber.prototype._next = function (value) { - var result; - try { - result = this.predicate.call(this.thisArg, value, this.count++); - } - catch (err) { - this.destination.error(err); - return; - } - if (result) { - this.destination.next(value); - } - }; - return FilterSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=filter.js.map - - -/***/ }), -/* 321 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return throwIfEmpty; }); -/* harmony import */ var _tap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(251); -/** PURE_IMPORTS_START _tap,_util_EmptyError PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(330); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); +/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */ -var throwIfEmpty = function (errorFactory) { + +function throwIfEmpty(errorFactory) { if (errorFactory === void 0) { errorFactory = defaultErrorFactory; } - return Object(_tap__WEBPACK_IMPORTED_MODULE_0__["tap"])({ - hasValue: false, - next: function () { this.hasValue = true; }, - complete: function () { - if (!this.hasValue) { - throw errorFactory(); - } - } - }); -}; -function defaultErrorFactory() { - return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__["EmptyError"](); -} -//# sourceMappingURL=throwIfEmpty.js.map - - -/***/ }), -/* 322 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return tap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(213); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(200); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ - - - - -function tap(nextOrObserver, error, complete) { - return function tapOperatorFunction(source) { - return source.lift(new DoOperator(nextOrObserver, error, complete)); + return function (source) { + return source.lift(new ThrowIfEmptyOperator(errorFactory)); }; } -var DoOperator = /*@__PURE__*/ (function () { - function DoOperator(nextOrObserver, error, complete) { - this.nextOrObserver = nextOrObserver; - this.error = error; - this.complete = complete; +var ThrowIfEmptyOperator = /*@__PURE__*/ (function () { + function ThrowIfEmptyOperator(errorFactory) { + this.errorFactory = errorFactory; } - DoOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TapSubscriber(subscriber, this.nextOrObserver, this.error, this.complete)); + ThrowIfEmptyOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ThrowIfEmptySubscriber(subscriber, this.errorFactory)); }; - return DoOperator; + return ThrowIfEmptyOperator; }()); -var TapSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TapSubscriber, _super); - function TapSubscriber(destination, observerOrNext, error, complete) { +var ThrowIfEmptySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrowIfEmptySubscriber, _super); + function ThrowIfEmptySubscriber(destination, errorFactory) { var _this = _super.call(this, destination) || this; - _this._tapNext = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapError = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapComplete = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapError = error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapComplete = complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_3__["isFunction"])(observerOrNext)) { - _this._context = _this; - _this._tapNext = observerOrNext; - } - else if (observerOrNext) { - _this._context = observerOrNext; - _this._tapNext = observerOrNext.next || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapError = observerOrNext.error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapComplete = observerOrNext.complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - } + _this.errorFactory = errorFactory; + _this.hasValue = false; return _this; } - TapSubscriber.prototype._next = function (value) { - try { - this._tapNext.call(this._context, value); - } - catch (err) { - this.destination.error(err); - return; - } + ThrowIfEmptySubscriber.prototype._next = function (value) { + this.hasValue = true; this.destination.next(value); }; - TapSubscriber.prototype._error = function (err) { - try { - this._tapError.call(this._context, err); - } - catch (err) { + ThrowIfEmptySubscriber.prototype._complete = function () { + if (!this.hasValue) { + var err = void 0; + try { + err = this.errorFactory(); + } + catch (e) { + err = e; + } this.destination.error(err); - return; - } - this.destination.error(err); - }; - TapSubscriber.prototype._complete = function () { - try { - this._tapComplete.call(this._context); } - catch (err) { - this.destination.error(err); - return; + else { + return this.destination.complete(); } - return this.destination.complete(); }; - return TapSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=tap.js.map + return ThrowIfEmptySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); +function defaultErrorFactory() { + return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__["EmptyError"](); +} +//# sourceMappingURL=throwIfEmpty.js.map /***/ }), -/* 323 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "take", function() { return take; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(329); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(310); /** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ @@ -34159,21 +40998,15 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 324 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return endWith; }); -/* harmony import */ var _observable_fromArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(234); -/* harmony import */ var _observable_scalar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(236); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(267); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(233); -/** PURE_IMPORTS_START _observable_fromArray,_observable_scalar,_observable_empty,_observable_concat,_util_isScheduler PURE_IMPORTS_END */ - - - +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); +/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(311); +/** PURE_IMPORTS_START _observable_concat,_observable_of PURE_IMPORTS_END */ function endWith() { @@ -34181,38 +41014,20 @@ function endWith() { for (var _i = 0; _i < arguments.length; _i++) { array[_i] = arguments[_i]; } - return function (source) { - var scheduler = array[array.length - 1]; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_4__["isScheduler"])(scheduler)) { - array.pop(); - } - else { - scheduler = null; - } - var len = array.length; - if (len === 1 && !scheduler) { - return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_3__["concat"])(source, Object(_observable_scalar__WEBPACK_IMPORTED_MODULE_1__["scalar"])(array[0])); - } - else if (len > 0) { - return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_3__["concat"])(source, Object(_observable_fromArray__WEBPACK_IMPORTED_MODULE_0__["fromArray"])(array, scheduler)); - } - else { - return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_3__["concat"])(source, Object(_observable_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(scheduler)); - } - }; + return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(source, _observable_of__WEBPACK_IMPORTED_MODULE_1__["of"].apply(void 0, array)); }; } //# sourceMappingURL=endWith.js.map /***/ }), -/* 325 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "every", function() { return every; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34267,15 +41082,15 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 326 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return exhaust; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -34324,18 +41139,20 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 327 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return exhaustMap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(268); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(350); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ + @@ -34343,20 +41160,20 @@ __webpack_require__.r(__webpack_exports__); function exhaustMap(project, resultSelector) { if (resultSelector) { - return function (source) { return source.pipe(exhaustMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_4__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; + return function (source) { return source.pipe(exhaustMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; } return function (source) { - return source.lift(new ExhauseMapOperator(project)); + return source.lift(new ExhaustMapOperator(project)); }; } -var ExhauseMapOperator = /*@__PURE__*/ (function () { - function ExhauseMapOperator(project) { +var ExhaustMapOperator = /*@__PURE__*/ (function () { + function ExhaustMapOperator(project) { this.project = project; } - ExhauseMapOperator.prototype.call = function (subscriber, source) { + ExhaustMapOperator.prototype.call = function (subscriber, source) { return source.subscribe(new ExhaustMapSubscriber(subscriber, this.project)); }; - return ExhauseMapOperator; + return ExhaustMapOperator; }()); var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ExhaustMapSubscriber, _super); @@ -34374,22 +41191,30 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { } }; ExhaustMapSubscriber.prototype.tryNext = function (value) { + var result; var index = this.index++; - var destination = this.destination; try { - var result = this.project(value, index); - this.hasSubscription = true; - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, result, value, index)); + result = this.project(value, index); } catch (err) { - destination.error(err); + this.destination.error(err); + return; } + this.hasSubscription = true; + this._innerSub(result, value, index); + }; + ExhaustMapSubscriber.prototype._innerSub = function (result, value, index) { + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber); }; ExhaustMapSubscriber.prototype._complete = function () { this.hasCompleted = true; if (!this.hasSubscription) { this.destination.complete(); } + this.unsubscribe(); }; ExhaustMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { this.destination.next(innerValue); @@ -34398,7 +41223,8 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { this.destination.error(err); }; ExhaustMapSubscriber.prototype.notifyComplete = function (innerSub) { - this.remove(innerSub); + var destination = this.destination; + destination.remove(innerSub); this.hasSubscription = false; if (this.hasCompleted) { this.destination.complete(); @@ -34410,7 +41236,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 328 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -34418,14 +41244,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return expand; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandOperator", function() { return ExpandOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandSubscriber", function() { return ExpandSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -34479,16 +41301,20 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { var index = this.index++; if (this.active < this.concurrent) { destination.next(value); - var result = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_1__["tryCatch"])(this.project)(value, index); - if (result === _util_errorObject__WEBPACK_IMPORTED_MODULE_2__["errorObject"]) { - destination.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_2__["errorObject"].e); - } - else if (!this.scheduler) { - this.subscribeToProjection(result, value, index); + try { + var project = this.project; + var result = project(value, index); + if (!this.scheduler) { + this.subscribeToProjection(result, value, index); + } + else { + var state = { subscriber: this, result: result, value: value, index: index }; + var destination_1 = this.destination; + destination_1.add(this.scheduler.schedule(ExpandSubscriber.dispatch, 0, state)); + } } - else { - var state = { subscriber: this, result: result, value: value, index: index }; - this.add(this.scheduler.schedule(ExpandSubscriber.dispatch, 0, state)); + catch (e) { + destination.error(e); } } else { @@ -34497,20 +41323,23 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { }; ExpandSubscriber.prototype.subscribeToProjection = function (result, value, index) { this.active++; - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, result, value, index)); + var destination = this.destination; + destination.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, result, value, index)); }; ExpandSubscriber.prototype._complete = function () { this.hasCompleted = true; if (this.hasCompleted && this.active === 0) { this.destination.complete(); } + this.unsubscribe(); }; ExpandSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { this._next(innerValue); }; ExpandSubscriber.prototype.notifyComplete = function (innerSub) { var buffer = this.buffer; - this.remove(innerSub); + var destination = this.destination; + destination.remove(innerSub); this.active--; if (buffer && buffer.length > 0) { this._next(buffer.shift()); @@ -34520,21 +41349,21 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { } }; return ExpandSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); //# sourceMappingURL=expand.js.map /***/ }), -/* 329 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return finalize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */ @@ -34564,7 +41393,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 330 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -34572,8 +41401,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "find", function() { return find; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueOperator", function() { return FindValueOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueSubscriber", function() { return FindValueSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34611,6 +41440,7 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { var destination = this.destination; destination.next(value); destination.complete(); + this.unsubscribe(); }; FindValueSubscriber.prototype._next = function (value) { var _a = this, predicate = _a.predicate, thisArg = _a.thisArg; @@ -34635,13 +41465,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 331 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(411); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -34651,18 +41481,18 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 332 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(251); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(323); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(321); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(404); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(394); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(403); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(327); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -34678,14 +41508,14 @@ function first(predicate, defaultValue) { /***/ }), -/* 333 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return ignoreElements; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34715,14 +41545,14 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 334 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return isEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34759,18 +41589,18 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 335 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(251); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(321); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(311); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(403); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(394); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(327); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -34786,16 +41616,16 @@ function last(predicate, defaultValue) { /***/ }), -/* 336 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return takeLast; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(329); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(310); /** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ @@ -34863,14 +41693,14 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 337 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return mapTo; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34902,15 +41732,15 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 338 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return materialize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(230); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(309); /** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -34952,13 +41782,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 339 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(340); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -34971,16 +41801,16 @@ function max(comparer) { /***/ }), -/* 340 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(341); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(212); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(422); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(417); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(394); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(291); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -34993,23 +41823,21 @@ function reduce(accumulator, seed) { }; } return function reduceOperatorFunction(source) { - return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipe"])(Object(_scan__WEBPACK_IMPORTED_MODULE_0__["scan"])(function (acc, value, index) { - return accumulator(acc, value, index + 1); - }), Object(_takeLast__WEBPACK_IMPORTED_MODULE_1__["takeLast"])(1))(source); + return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipe"])(Object(_scan__WEBPACK_IMPORTED_MODULE_0__["scan"])(function (acc, value, index) { return accumulator(acc, value, index + 1); }), Object(_takeLast__WEBPACK_IMPORTED_MODULE_1__["takeLast"])(1))(source); }; } //# sourceMappingURL=reduce.js.map /***/ }), -/* 341 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return scan; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -35084,13 +41912,13 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 342 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); -/* harmony import */ var _observable_merge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _observable_merge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(365); /** PURE_IMPORTS_START _observable_merge PURE_IMPORTS_END */ function merge() { @@ -35104,13 +41932,13 @@ function merge() { /***/ }), -/* 343 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return mergeMapTo; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(349); /** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ function mergeMapTo(innerObservable, resultSelector, concurrent) { @@ -35129,7 +41957,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 344 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -35137,13 +41965,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return mergeScan; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanOperator", function() { return MergeScanOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanSubscriber", function() { return MergeScanSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(208); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/** PURE_IMPORTS_START tslib,_util_tryCatch,_util_errorObject,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(338); +/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber PURE_IMPORTS_END */ @@ -35183,22 +42009,27 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { MergeScanSubscriber.prototype._next = function (value) { if (this.active < this.concurrent) { var index = this.index++; - var ish = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_1__["tryCatch"])(this.accumulator)(this.acc, value); var destination = this.destination; - if (ish === _util_errorObject__WEBPACK_IMPORTED_MODULE_2__["errorObject"]) { - destination.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_2__["errorObject"].e); + var ish = void 0; + try { + var accumulator = this.accumulator; + ish = accumulator(this.acc, value, index); } - else { - this.active++; - this._innerSub(ish, value, index); + catch (e) { + return destination.error(e); } + this.active++; + this._innerSub(ish, value, index); } else { this.buffer.push(value); } }; MergeScanSubscriber.prototype._innerSub = function (ish, value, index) { - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, ish, value, index)); + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber); }; MergeScanSubscriber.prototype._complete = function () { this.hasCompleted = true; @@ -35208,6 +42039,7 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { } this.destination.complete(); } + this.unsubscribe(); }; MergeScanSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { var destination = this.destination; @@ -35217,7 +42049,8 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { }; MergeScanSubscriber.prototype.notifyComplete = function (innerSub) { var buffer = this.buffer; - this.remove(innerSub); + var destination = this.destination; + destination.remove(innerSub); this.active--; if (buffer.length > 0) { this._next(buffer.shift()); @@ -35230,19 +42063,19 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { } }; return MergeScanSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); //# sourceMappingURL=mergeScan.js.map /***/ }), -/* 345 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(340); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -35255,14 +42088,14 @@ function min(comparer) { /***/ }), -/* 346 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return multicast; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MulticastOperator", function() { return MulticastOperator; }); -/* harmony import */ var _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(214); +/* harmony import */ var _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); /** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */ function multicast(subjectOrSubjectFactory, selector) { @@ -35304,19 +42137,21 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 347 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNextStatic", function() { return onErrorResumeNextStatic; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + @@ -35369,14 +42204,19 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { }; OnErrorResumeNextSubscriber.prototype._error = function (err) { this.subscribeToNextSource(); + this.unsubscribe(); }; OnErrorResumeNextSubscriber.prototype._complete = function () { this.subscribeToNextSource(); + this.unsubscribe(); }; OnErrorResumeNextSubscriber.prototype.subscribeToNextSource = function () { var next = this.nextSources.shift(); - if (next) { - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, next)); + if (!!next) { + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, next, undefined, undefined, innerSubscriber); } else { this.destination.complete(); @@ -35388,14 +42228,14 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 348 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return pairwise; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -35418,13 +42258,17 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { return _this; } PairwiseSubscriber.prototype._next = function (value) { + var pair; if (this.hasPrev) { - this.destination.next([this.prev, value]); + pair = [this.prev, value]; } else { this.hasPrev = true; } this.prev = value; + if (pair) { + this.destination.next(pair); + } }; return PairwiseSubscriber; }(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); @@ -35432,14 +42276,14 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 349 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); -/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(350); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); +/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(370); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); /** PURE_IMPORTS_START _util_not,_filter PURE_IMPORTS_END */ @@ -35455,32 +42299,13 @@ function partition(predicate, thisArg) { /***/ }), -/* 350 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "not", function() { return not; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function not(pred, thisArg) { - function notPred() { - return !(notPred.pred.apply(notPred.thisArg, arguments)); - } - notPred.pred = pred; - notPred.thisArg = thisArg; - return notPred; -} -//# sourceMappingURL=not.js.map - - -/***/ }), -/* 351 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return pluck; }); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(254); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(333); /** PURE_IMPORTS_START _map PURE_IMPORTS_END */ function pluck() { @@ -35514,14 +42339,14 @@ function plucker(props, length) { /***/ }), -/* 352 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(215); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(294); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -35534,14 +42359,14 @@ function publish(selector) { /***/ }), -/* 353 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); -/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(220); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(299); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -35552,14 +42377,14 @@ function publishBehavior(value) { /***/ }), -/* 354 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(238); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(317); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -35570,14 +42395,14 @@ function publishLast() { /***/ }), -/* 355 */ +/* 435 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(221); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(300); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -35593,14 +42418,14 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 356 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); -/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(289); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(372); /** PURE_IMPORTS_START _util_isArray,_observable_race PURE_IMPORTS_END */ @@ -35620,15 +42445,15 @@ function race() { /***/ }), -/* 357 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return repeat; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); /** PURE_IMPORTS_START tslib,_Subscriber,_observable_empty PURE_IMPORTS_END */ @@ -35685,21 +42510,17 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 358 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return repeatWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_Subject,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35768,27 +42589,31 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { }; RepeatWhenSubscriber.prototype.subscribeToRetries = function () { this.notifications = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - var retries = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(this.notifier)(this.notifications); - if (retries === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { + var retries; + try { + var notifier = this.notifier; + retries = notifier(this.notifications); + } + catch (e) { return _super.prototype.complete.call(this); } this.retries = retries; - this.retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, retries); + this.retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, retries); }; return RepeatWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); //# sourceMappingURL=repeatWhen.js.map /***/ }), -/* 359 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return retry; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -35834,21 +42659,17 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 360 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return retryWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_Subject,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35881,11 +42702,14 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { var retriesSubscription = this.retriesSubscription; if (!retries) { errors = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - retries = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(this.notifier)(errors); - if (retries === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { - return _super.prototype.error.call(this, _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"].e); + try { + var notifier = this.notifier; + retries = notifier(errors); } - retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, retries); + catch (e) { + return _super.prototype.error.call(this, e); + } + retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, retries); } else { this.errors = null; @@ -35918,20 +42742,20 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { this.source.subscribe(this); }; return RetryWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); //# sourceMappingURL=retryWhen.js.map /***/ }), -/* 361 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return sample; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35980,15 +42804,15 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 362 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return sampleTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ @@ -36040,7 +42864,7 @@ function dispatchNotification(state) { /***/ }), -/* 363 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -36048,39 +42872,35 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return sequenceEqual; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualOperator", function() { return SequenceEqualOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualSubscriber", function() { return SequenceEqualSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_tryCatch,_util_errorObject PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ -function sequenceEqual(compareTo, comparor) { - return function (source) { return source.lift(new SequenceEqualOperator(compareTo, comparor)); }; +function sequenceEqual(compareTo, comparator) { + return function (source) { return source.lift(new SequenceEqualOperator(compareTo, comparator)); }; } var SequenceEqualOperator = /*@__PURE__*/ (function () { - function SequenceEqualOperator(compareTo, comparor) { + function SequenceEqualOperator(compareTo, comparator) { this.compareTo = compareTo; - this.comparor = comparor; + this.comparator = comparator; } SequenceEqualOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SequenceEqualSubscriber(subscriber, this.compareTo, this.comparor)); + return source.subscribe(new SequenceEqualSubscriber(subscriber, this.compareTo, this.comparator)); }; return SequenceEqualOperator; }()); var SequenceEqualSubscriber = /*@__PURE__*/ (function (_super) { tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SequenceEqualSubscriber, _super); - function SequenceEqualSubscriber(destination, compareTo, comparor) { + function SequenceEqualSubscriber(destination, compareTo, comparator) { var _this = _super.call(this, destination) || this; _this.compareTo = compareTo; - _this.comparor = comparor; + _this.comparator = comparator; _this._a = []; _this._b = []; _this._oneComplete = false; - _this.add(compareTo.subscribe(new SequenceEqualCompareToSubscriber(destination, _this))); + _this.destination.add(compareTo.subscribe(new SequenceEqualCompareToSubscriber(destination, _this))); return _this; } SequenceEqualSubscriber.prototype._next = function (value) { @@ -36099,21 +42919,19 @@ var SequenceEqualSubscriber = /*@__PURE__*/ (function (_super) { else { this._oneComplete = true; } + this.unsubscribe(); }; SequenceEqualSubscriber.prototype.checkValues = function () { - var _c = this, _a = _c._a, _b = _c._b, comparor = _c.comparor; + var _c = this, _a = _c._a, _b = _c._b, comparator = _c.comparator; while (_a.length > 0 && _b.length > 0) { var a = _a.shift(); var b = _b.shift(); var areEqual = false; - if (comparor) { - areEqual = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(comparor)(a, b); - if (areEqual === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { - this.destination.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"].e); - } + try { + areEqual = comparator ? comparator(a, b) : a === b; } - else { - areEqual = a === b; + catch (e) { + this.destination.error(e); } if (!areEqual) { this.emit(false); @@ -36134,6 +42952,14 @@ var SequenceEqualSubscriber = /*@__PURE__*/ (function (_super) { this.checkValues(); } }; + SequenceEqualSubscriber.prototype.completeB = function () { + if (this._oneComplete) { + this.emit(this._a.length === 0 && this._b.length === 0); + } + else { + this._oneComplete = true; + } + }; return SequenceEqualSubscriber; }(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); @@ -36149,9 +42975,11 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { }; SequenceEqualCompareToSubscriber.prototype._error = function (err) { this.parent.error(err); + this.unsubscribe(); }; SequenceEqualCompareToSubscriber.prototype._complete = function () { - this.parent._complete(); + this.parent.completeB(); + this.unsubscribe(); }; return SequenceEqualCompareToSubscriber; }(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); @@ -36159,15 +42987,15 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 364 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); -/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(218); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(215); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(427); +/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(297); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(294); /** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ @@ -36182,19 +43010,32 @@ function share() { /***/ }), -/* 365 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return shareReplay; }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(221); +/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(300); /** PURE_IMPORTS_START _ReplaySubject PURE_IMPORTS_END */ -function shareReplay(bufferSize, windowTime, scheduler) { - return function (source) { return source.lift(shareReplayOperator(bufferSize, windowTime, scheduler)); }; +function shareReplay(configOrBufferSize, windowTime, scheduler) { + var config; + if (configOrBufferSize && typeof configOrBufferSize === 'object') { + config = configOrBufferSize; + } + else { + config = { + bufferSize: configOrBufferSize, + windowTime: windowTime, + refCount: false, + scheduler: scheduler + }; + } + return function (source) { return source.lift(shareReplayOperator(config)); }; } -function shareReplayOperator(bufferSize, windowTime, scheduler) { +function shareReplayOperator(_a) { + var _b = _a.bufferSize, bufferSize = _b === void 0 ? Number.POSITIVE_INFINITY : _b, _c = _a.windowTime, windowTime = _c === void 0 ? Number.POSITIVE_INFINITY : _c, useRefCount = _a.refCount, scheduler = _a.scheduler; var subject; var refCount = 0; var subscription; @@ -36218,28 +43059,30 @@ function shareReplayOperator(bufferSize, windowTime, scheduler) { }); } var innerSub = subject.subscribe(this); - return function () { + this.add(function () { refCount--; innerSub.unsubscribe(); - if (subscription && refCount === 0 && isComplete) { + if (subscription && !isComplete && useRefCount && refCount === 0) { subscription.unsubscribe(); + subscription = undefined; + subject = undefined; } - }; + }); }; } //# sourceMappingURL=shareReplay.js.map /***/ }), -/* 366 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "single", function() { return single; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(251); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(330); /** PURE_IMPORTS_START tslib,_Subscriber,_util_EmptyError PURE_IMPORTS_END */ @@ -36311,14 +43154,14 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 367 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return skip; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -36353,15 +43196,15 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 368 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return skipLast; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(329); /** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError PURE_IMPORTS_END */ @@ -36415,16 +43258,18 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 369 */ +/* 449 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return skipUntil; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + @@ -36445,7 +43290,10 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { function SkipUntilSubscriber(destination, notifier) { var _this = _super.call(this, destination) || this; _this.hasValue = false; - _this.add(_this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, notifier)); + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](_this, undefined, undefined); + _this.add(innerSubscriber); + _this.innerSubscription = innerSubscriber; + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(_this, notifier, undefined, undefined, innerSubscriber); return _this; } SkipUntilSubscriber.prototype._next = function (value) { @@ -36467,14 +43315,14 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 370 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return skipWhile; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -36523,21 +43371,15 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 371 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return startWith; }); -/* harmony import */ var _observable_fromArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(234); -/* harmony import */ var _observable_scalar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(236); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(267); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(233); -/** PURE_IMPORTS_START _observable_fromArray,_observable_scalar,_observable_empty,_observable_concat,_util_isScheduler PURE_IMPORTS_END */ - - - +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); +/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */ function startWith() { @@ -36545,37 +43387,26 @@ function startWith() { for (var _i = 0; _i < arguments.length; _i++) { array[_i] = arguments[_i]; } - return function (source) { - var scheduler = array[array.length - 1]; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_4__["isScheduler"])(scheduler)) { - array.pop(); - } - else { - scheduler = null; - } - var len = array.length; - if (len === 1 && !scheduler) { - return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_3__["concat"])(Object(_observable_scalar__WEBPACK_IMPORTED_MODULE_1__["scalar"])(array[0]), source); - } - else if (len > 0) { - return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_3__["concat"])(Object(_observable_fromArray__WEBPACK_IMPORTED_MODULE_0__["fromArray"])(array, scheduler), source); - } - else { - return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_3__["concat"])(Object(_observable_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(scheduler), source); - } - }; + var scheduler = array[array.length - 1]; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(scheduler)) { + array.pop(); + return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(array, source, scheduler); }; + } + else { + return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(array, source); }; + } } //# sourceMappingURL=startWith.js.map /***/ }), -/* 372 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(373); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(453); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -36600,16 +43431,16 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 373 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubscribeOnObservable", function() { return SubscribeOnObservable; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(196); -/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); +/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(318); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(364); /** PURE_IMPORTS_START tslib,_Observable,_scheduler_asap,_util_isNumeric PURE_IMPORTS_END */ @@ -36664,14 +43495,14 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 374 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(375); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(248); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(327); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -36682,18 +43513,20 @@ function switchAll() { /***/ }), -/* 375 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return switchMap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(268); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(350); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ + @@ -36701,7 +43534,7 @@ __webpack_require__.r(__webpack_exports__); function switchMap(project, resultSelector) { if (typeof resultSelector === 'function') { - return function (source) { return source.pipe(switchMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_4__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; + return function (source) { return source.pipe(switchMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; } return function (source) { return source.lift(new SwitchMapOperator(project)); }; } @@ -36739,19 +43572,24 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { if (innerSubscription) { innerSubscription.unsubscribe(); } - this.add(this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, result, value, index)); + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber); }; SwitchMapSubscriber.prototype._complete = function () { var innerSubscription = this.innerSubscription; if (!innerSubscription || innerSubscription.closed) { _super.prototype._complete.call(this); } + this.unsubscribe(); }; SwitchMapSubscriber.prototype._unsubscribe = function () { this.innerSubscription = null; }; SwitchMapSubscriber.prototype.notifyComplete = function (innerSub) { - this.remove(innerSub); + var destination = this.destination; + destination.remove(innerSub); this.innerSubscription = null; if (this.isStopped) { _super.prototype._complete.call(this); @@ -36766,13 +43604,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 376 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(375); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -36782,15 +43620,15 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 377 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return takeUntil; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -36805,7 +43643,7 @@ var TakeUntilOperator = /*@__PURE__*/ (function () { TakeUntilOperator.prototype.call = function (subscriber, source) { var takeUntilSubscriber = new TakeUntilSubscriber(subscriber); var notifierSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(takeUntilSubscriber, this.notifier); - if (notifierSubscription && !notifierSubscription.closed) { + if (notifierSubscription && !takeUntilSubscriber.seenValue) { takeUntilSubscriber.add(notifierSubscription); return source.subscribe(takeUntilSubscriber); } @@ -36816,9 +43654,12 @@ var TakeUntilOperator = /*@__PURE__*/ (function () { var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeUntilSubscriber, _super); function TakeUntilSubscriber(destination) { - return _super.call(this, destination) || this; + var _this = _super.call(this, destination) || this; + _this.seenValue = false; + return _this; } TakeUntilSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.seenValue = true; this.complete(); }; TakeUntilSubscriber.prototype.notifyComplete = function () { @@ -36829,34 +43670,41 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 378 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return takeWhile; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ -function takeWhile(predicate) { - return function (source) { return source.lift(new TakeWhileOperator(predicate)); }; +function takeWhile(predicate, inclusive) { + if (inclusive === void 0) { + inclusive = false; + } + return function (source) { + return source.lift(new TakeWhileOperator(predicate, inclusive)); + }; } var TakeWhileOperator = /*@__PURE__*/ (function () { - function TakeWhileOperator(predicate) { + function TakeWhileOperator(predicate, inclusive) { this.predicate = predicate; + this.inclusive = inclusive; } TakeWhileOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate)); + return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate, this.inclusive)); }; return TakeWhileOperator; }()); var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeWhileSubscriber, _super); - function TakeWhileSubscriber(destination, predicate) { + function TakeWhileSubscriber(destination, predicate, inclusive) { var _this = _super.call(this, destination) || this; _this.predicate = predicate; + _this.inclusive = inclusive; _this.index = 0; return _this; } @@ -36878,6 +43726,9 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { destination.next(value); } else { + if (this.inclusive) { + destination.next(value); + } destination.complete(); } }; @@ -36887,16 +43738,104 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 379 */ +/* 459 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return tap; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(292); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(280); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ + + + + +function tap(nextOrObserver, error, complete) { + return function tapOperatorFunction(source) { + return source.lift(new DoOperator(nextOrObserver, error, complete)); + }; +} +var DoOperator = /*@__PURE__*/ (function () { + function DoOperator(nextOrObserver, error, complete) { + this.nextOrObserver = nextOrObserver; + this.error = error; + this.complete = complete; + } + DoOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new TapSubscriber(subscriber, this.nextOrObserver, this.error, this.complete)); + }; + return DoOperator; +}()); +var TapSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TapSubscriber, _super); + function TapSubscriber(destination, observerOrNext, error, complete) { + var _this = _super.call(this, destination) || this; + _this._tapNext = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapError = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapComplete = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapError = error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapComplete = complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_3__["isFunction"])(observerOrNext)) { + _this._context = _this; + _this._tapNext = observerOrNext; + } + else if (observerOrNext) { + _this._context = observerOrNext; + _this._tapNext = observerOrNext.next || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapError = observerOrNext.error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapComplete = observerOrNext.complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + } + return _this; + } + TapSubscriber.prototype._next = function (value) { + try { + this._tapNext.call(this._context, value); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.next(value); + }; + TapSubscriber.prototype._error = function (err) { + try { + this._tapError.call(this._context, err); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.error(err); + }; + TapSubscriber.prototype._complete = function () { + try { + this._tapComplete.call(this._context); + } + catch (err) { + this.destination.error(err); + return; + } + return this.destination.complete(); + }; + return TapSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=tap.js.map + + +/***/ }), +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultThrottleConfig", function() { return defaultThrottleConfig; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return throttle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -36956,7 +43895,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { }; ThrottleSubscriber.prototype.throttle = function (value) { var duration = this.tryDurationSelector(value); - if (duration) { + if (!!duration) { this.add(this._throttled = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration)); } }; @@ -36991,16 +43930,16 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 380 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return throttleTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(379); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(460); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -37051,6 +43990,10 @@ var ThrottleTimeSubscriber = /*@__PURE__*/ (function (_super) { if (this.leading) { this.destination.next(value); } + else if (this.trailing) { + this._trailingValue = value; + this._hasTrailingValue = true; + } } }; ThrottleTimeSubscriber.prototype._complete = function () { @@ -37085,17 +44028,17 @@ function dispatchNext(arg) { /***/ }), -/* 381 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(341); -/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(277); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(422); +/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(357); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -37129,16 +44072,16 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 382 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(252); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(383); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(237); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); +/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(331); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(464); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(316); /** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ @@ -37154,17 +44097,17 @@ function timeout(due, scheduler) { /***/ }), -/* 383 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37236,15 +44179,15 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 384 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return timestamp; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Timestamp", function() { return Timestamp; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(254); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(333); /** PURE_IMPORTS_START _scheduler_async,_map PURE_IMPORTS_END */ @@ -37266,13 +44209,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 385 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(340); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -37289,16 +44232,16 @@ function toArray() { /***/ }), -/* 386 */ +/* 467 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "window", function() { return window; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37369,15 +44312,15 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 387 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return windowCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(215); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(294); /** PURE_IMPORTS_START tslib,_Subscriber,_Subject PURE_IMPORTS_END */ @@ -37459,18 +44402,18 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 388 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return windowTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(284); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(233); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(364); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(312); /** PURE_IMPORTS_START tslib,_Subject,_scheduler_async,_Subscriber,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ @@ -37629,22 +44572,18 @@ function dispatchWindowClose(state) { /***/ }), -/* 389 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return windowToggle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_Subject,_Subscription,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_Subject,_Subscription,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37670,7 +44609,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { _this.openings = openings; _this.closingSelector = closingSelector; _this.contexts = []; - _this.add(_this.openSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_6__["subscribeToResult"])(_this, openings, openings)); + _this.add(_this.openSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(_this, openings, openings)); return _this; } WindowToggleSubscriber.prototype._next = function (value) { @@ -37725,26 +44664,27 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { }; WindowToggleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { if (outerValue === this.openings) { - var closingSelector = this.closingSelector; - var closingNotifier = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_3__["tryCatch"])(closingSelector)(innerValue); - if (closingNotifier === _util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"]) { - return this.error(_util_errorObject__WEBPACK_IMPORTED_MODULE_4__["errorObject"].e); + var closingNotifier = void 0; + try { + var closingSelector = this.closingSelector; + closingNotifier = closingSelector(innerValue); + } + catch (e) { + return this.error(e); + } + var window_1 = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](); + var context_4 = { window: window_1, subscription: subscription }; + this.contexts.push(context_4); + var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, closingNotifier, context_4); + if (innerSubscription.closed) { + this.closeWindow(this.contexts.length - 1); } else { - var window_1 = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](); - var context_4 = { window: window_1, subscription: subscription }; - this.contexts.push(context_4); - var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_6__["subscribeToResult"])(this, closingNotifier, context_4); - if (innerSubscription.closed) { - this.closeWindow(this.contexts.length - 1); - } - else { - innerSubscription.context = context_4; - subscription.add(innerSubscription); - } - this.destination.next(window_1); + innerSubscription.context = context_4; + subscription.add(innerSubscription); } + this.destination.next(window_1); } else { this.closeWindow(this.contexts.indexOf(outerValue)); @@ -37770,26 +44710,22 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { subscription.unsubscribe(); }; return WindowToggleSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); //# sourceMappingURL=windowToggle.js.map /***/ }), -/* 390 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return windowWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); -/** PURE_IMPORTS_START tslib,_Subject,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37858,31 +44794,33 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { } var window = this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); this.destination.next(window); - var closingNotifier = Object(_util_tryCatch__WEBPACK_IMPORTED_MODULE_2__["tryCatch"])(this.closingSelector)(); - if (closingNotifier === _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"]) { - var err = _util_errorObject__WEBPACK_IMPORTED_MODULE_3__["errorObject"].e; - this.destination.error(err); - this.window.error(err); + var closingNotifier; + try { + var closingSelector = this.closingSelector; + closingNotifier = closingSelector(); } - else { - this.add(this.closingNotification = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, closingNotifier)); + catch (e) { + this.destination.error(e); + this.window.error(e); + return; } + this.add(this.closingNotification = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, closingNotifier)); }; return WindowSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); //# sourceMappingURL=windowWhen.js.map /***/ }), -/* 391 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return withLatestFrom; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37969,13 +44907,13 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 392 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); -/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); +/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(376); /** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ function zip() { @@ -37991,13 +44929,13 @@ function zip() { /***/ }), -/* 393 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return zipAll; }); -/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); +/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(376); /** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ function zipAll(project) { @@ -38007,7 +44945,7 @@ function zipAll(project) { /***/ }), -/* 394 */ +/* 475 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -38015,15 +44953,15 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(395); +/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(259); /* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(indent_string__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); +/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(476); /* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(wrap_ansi__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(403); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(483); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -38101,47 +45039,13 @@ function toArray(value) { } /***/ }), -/* 395 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = (str, count, opts) => { - // Support older versions: use the third parameter as options.indent - // TODO: Remove the workaround in the next major version - const options = typeof opts === 'object' ? Object.assign({indent: ' '}, opts) : {indent: opts || ' '}; - count = count === undefined ? 1 : count; - - if (typeof str !== 'string') { - throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof str}\``); - } - - if (typeof count !== 'number') { - throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); - } - - if (typeof options.indent !== 'string') { - throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``); - } - - if (count === 0) { - return str; - } - - const regex = options.includeEmptyLines ? /^/mg : /^(?!\s*$)/mg; - return str.replace(regex, options.indent.repeat(count)); -} -; - - -/***/ }), -/* 396 */ +/* 476 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringWidth = __webpack_require__(397); -const stripAnsi = __webpack_require__(401); +const stringWidth = __webpack_require__(477); +const stripAnsi = __webpack_require__(481); const ESCAPES = new Set([ '\u001B', @@ -38335,13 +45239,13 @@ module.exports = (str, cols, opts) => { /***/ }), -/* 397 */ +/* 477 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stripAnsi = __webpack_require__(398); -const isFullwidthCodePoint = __webpack_require__(400); +const stripAnsi = __webpack_require__(478); +const isFullwidthCodePoint = __webpack_require__(480); module.exports = str => { if (typeof str !== 'string' || str.length === 0) { @@ -38378,18 +45282,18 @@ module.exports = str => { /***/ }), -/* 398 */ +/* 478 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(399); +const ansiRegex = __webpack_require__(479); module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; /***/ }), -/* 399 */ +/* 479 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38406,7 +45310,7 @@ module.exports = () => { /***/ }), -/* 400 */ +/* 480 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38459,18 +45363,18 @@ module.exports = x => { /***/ }), -/* 401 */ +/* 481 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(402); +const ansiRegex = __webpack_require__(482); module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; /***/ }), -/* 402 */ +/* 482 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38487,7 +45391,7 @@ module.exports = () => { /***/ }), -/* 403 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -38640,15 +45544,15 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 404 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(405); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(613); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(692); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -38673,19 +45577,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 405 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(406); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(486); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(166); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(174); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(55); @@ -38819,17 +45723,17 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 406 */ +/* 486 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const EventEmitter = __webpack_require__(46); const path = __webpack_require__(16); -const arrify = __webpack_require__(407); -const globby = __webpack_require__(408); -const cpFile = __webpack_require__(603); -const CpyError = __webpack_require__(611); +const arrify = __webpack_require__(487); +const globby = __webpack_require__(488); +const cpFile = __webpack_require__(682); +const CpyError = __webpack_require__(690); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -38934,7 +45838,7 @@ module.exports.default = cpy; /***/ }), -/* 407 */ +/* 487 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38949,17 +45853,17 @@ module.exports = function (val) { /***/ }), -/* 408 */ +/* 488 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const arrayUnion = __webpack_require__(170); +const arrayUnion = __webpack_require__(489); const glob = __webpack_require__(37); -const fastGlob = __webpack_require__(409); -const dirGlob = __webpack_require__(596); -const gitignore = __webpack_require__(599); +const fastGlob = __webpack_require__(491); +const dirGlob = __webpack_require__(675); +const gitignore = __webpack_require__(678); const DEFAULT_FILTER = () => false; @@ -39104,10 +46008,92 @@ module.exports.gitignore = gitignore; /***/ }), -/* 409 */ +/* 489 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +var arrayUniq = __webpack_require__(490); + +module.exports = function () { + return arrayUniq([].concat.apply([], arguments)); +}; + + +/***/ }), +/* 490 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +// there's 3 implementations written in increasing order of efficiency + +// 1 - no Set type is defined +function uniqNoSet(arr) { + var ret = []; + + for (var i = 0; i < arr.length; i++) { + if (ret.indexOf(arr[i]) === -1) { + ret.push(arr[i]); + } + } + + return ret; +} + +// 2 - a simple Set type is defined +function uniqSet(arr) { + var seen = new Set(); + return arr.filter(function (el) { + if (!seen.has(el)) { + seen.add(el); + return true; + } + + return false; + }); +} + +// 3 - a standard Set type is defined and it has a forEach method +function uniqSetWithForEach(arr) { + var ret = []; + + (new Set(arr)).forEach(function (el) { + ret.push(el); + }); + + return ret; +} + +// V8 currently has a broken implementation +// https://github.com/joyent/node/issues/8449 +function doesForEachActuallyWork() { + var ret = false; + + (new Set([true])).forEach(function (el) { + ret = el; + }); + + return ret === true; +} + +if ('Set' in global) { + if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { + module.exports = uniqSetWithForEach; + } else { + module.exports = uniqSet; + } +} else { + module.exports = uniqNoSet; +} + + +/***/ }), +/* 491 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(410); +const pkg = __webpack_require__(492); module.exports = pkg.async; module.exports.default = pkg.async; @@ -39120,19 +46106,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 410 */ +/* 492 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(411); -var taskManager = __webpack_require__(412); -var reader_async_1 = __webpack_require__(566); -var reader_stream_1 = __webpack_require__(590); -var reader_sync_1 = __webpack_require__(591); -var arrayUtils = __webpack_require__(593); -var streamUtils = __webpack_require__(594); +var optionsManager = __webpack_require__(493); +var taskManager = __webpack_require__(494); +var reader_async_1 = __webpack_require__(646); +var reader_stream_1 = __webpack_require__(670); +var reader_sync_1 = __webpack_require__(671); +var arrayUtils = __webpack_require__(673); +var streamUtils = __webpack_require__(674); /** * Synchronous API. */ @@ -39198,7 +46184,7 @@ function isString(source) { /***/ }), -/* 411 */ +/* 493 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39236,13 +46222,13 @@ exports.prepare = prepare; /***/ }), -/* 412 */ +/* 494 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(413); +var patternUtils = __webpack_require__(495); /** * Generate tasks based on parent directory of each pattern. */ @@ -39333,16 +46319,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 413 */ +/* 495 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var globParent = __webpack_require__(414); -var isGlob = __webpack_require__(418); -var micromatch = __webpack_require__(419); +var globParent = __webpack_require__(496); +var isGlob = __webpack_require__(499); +var micromatch = __webpack_require__(500); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -39488,15 +46474,15 @@ exports.matchAny = matchAny; /***/ }), -/* 414 */ +/* 496 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(16); -var isglob = __webpack_require__(415); -var pathDirname = __webpack_require__(417); +var isglob = __webpack_require__(497); +var pathDirname = __webpack_require__(498); var isWin32 = __webpack_require__(11).platform() === 'win32'; module.exports = function globParent(str) { @@ -39519,7 +46505,7 @@ module.exports = function globParent(str) { /***/ }), -/* 415 */ +/* 497 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -39529,7 +46515,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(416); +var isExtglob = __webpack_require__(188); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -39550,33 +46536,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 416 */ -/***/ (function(module, exports) { - -/*! - * is-extglob - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License. - */ - -module.exports = function isExtglob(str) { - if (typeof str !== 'string' || str === '') { - return false; - } - - var match; - while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { - if (match[2]) return true; - str = str.slice(match.index + match[0].length); - } - - return false; -}; - - -/***/ }), -/* 417 */ +/* 498 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39726,7 +46686,7 @@ module.exports.win32 = win32; /***/ }), -/* 418 */ +/* 499 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -39736,7 +46696,7 @@ module.exports.win32 = win32; * Released under the MIT License. */ -var isExtglob = __webpack_require__(416); +var isExtglob = __webpack_require__(188); var chars = { '{': '}', '(': ')', '[': ']'}; module.exports = function isGlob(str, options) { @@ -39778,7 +46738,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 419 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39789,18 +46749,18 @@ module.exports = function isGlob(str, options) { */ var util = __webpack_require__(29); -var braces = __webpack_require__(420); -var toRegex = __webpack_require__(524); -var extend = __webpack_require__(532); +var braces = __webpack_require__(501); +var toRegex = __webpack_require__(604); +var extend = __webpack_require__(612); /** * Local dependencies */ -var compilers = __webpack_require__(535); -var parsers = __webpack_require__(562); -var cache = __webpack_require__(563); -var utils = __webpack_require__(564); +var compilers = __webpack_require__(615); +var parsers = __webpack_require__(642); +var cache = __webpack_require__(643); +var utils = __webpack_require__(644); var MAX_LENGTH = 1024 * 64; /** @@ -40662,7 +47622,7 @@ module.exports = micromatch; /***/ }), -/* 420 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40672,18 +47632,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(421); -var unique = __webpack_require__(433); -var extend = __webpack_require__(430); +var toRegex = __webpack_require__(502); +var unique = __webpack_require__(514); +var extend = __webpack_require__(511); /** * Local dependencies */ -var compilers = __webpack_require__(434); -var parsers = __webpack_require__(449); -var Braces = __webpack_require__(459); -var utils = __webpack_require__(435); +var compilers = __webpack_require__(515); +var parsers = __webpack_require__(530); +var Braces = __webpack_require__(540); +var utils = __webpack_require__(516); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -40987,15 +47947,15 @@ module.exports = braces; /***/ }), -/* 421 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(422); -var extend = __webpack_require__(430); -var not = __webpack_require__(432); +var define = __webpack_require__(503); +var extend = __webpack_require__(511); +var not = __webpack_require__(513); var MAX_LENGTH = 1024 * 64; /** @@ -41142,7 +48102,7 @@ module.exports.makeRe = makeRe; /***/ }), -/* 422 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41155,7 +48115,7 @@ module.exports.makeRe = makeRe; -var isDescriptor = __webpack_require__(423); +var isDescriptor = __webpack_require__(504); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -41180,7 +48140,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 423 */ +/* 504 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41193,9 +48153,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(424); -var isAccessor = __webpack_require__(425); -var isData = __webpack_require__(428); +var typeOf = __webpack_require__(505); +var isAccessor = __webpack_require__(506); +var isData = __webpack_require__(509); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -41209,7 +48169,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 424 */ +/* 505 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -41362,7 +48322,7 @@ function isBuffer(val) { /***/ }), -/* 425 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41375,7 +48335,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(426); +var typeOf = __webpack_require__(507); // accessor descriptor properties var accessor = { @@ -41438,10 +48398,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 426 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(508); var toString = Object.prototype.toString; /** @@ -41560,7 +48520,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 427 */ +/* 508 */ /***/ (function(module, exports) { /*! @@ -41587,7 +48547,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 428 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41600,7 +48560,7 @@ function isSlowBuffer (obj) { -var typeOf = __webpack_require__(429); +var typeOf = __webpack_require__(510); // data descriptor properties var data = { @@ -41649,10 +48609,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 429 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(508); var toString = Object.prototype.toString; /** @@ -41771,13 +48731,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 430 */ +/* 511 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(431); +var isObject = __webpack_require__(512); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -41811,7 +48771,7 @@ function hasOwn(obj, key) { /***/ }), -/* 431 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41831,13 +48791,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 432 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(430); +var extend = __webpack_require__(511); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -41904,7 +48864,7 @@ module.exports = toRegex; /***/ }), -/* 433 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41954,13 +48914,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 434 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(435); +var utils = __webpack_require__(516); module.exports = function(braces, options) { braces.compiler @@ -42243,25 +49203,25 @@ function hasQueue(node) { /***/ }), -/* 435 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(436); +var splitString = __webpack_require__(517); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(430); -utils.flatten = __webpack_require__(442); -utils.isObject = __webpack_require__(440); -utils.fillRange = __webpack_require__(443); -utils.repeat = __webpack_require__(448); -utils.unique = __webpack_require__(433); +utils.extend = __webpack_require__(511); +utils.flatten = __webpack_require__(523); +utils.isObject = __webpack_require__(521); +utils.fillRange = __webpack_require__(524); +utils.repeat = __webpack_require__(529); +utils.unique = __webpack_require__(514); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -42593,7 +49553,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 436 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42606,7 +49566,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(437); +var extend = __webpack_require__(518); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -42771,14 +49731,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 437 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(438); -var assignSymbols = __webpack_require__(441); +var isExtendable = __webpack_require__(519); +var assignSymbols = __webpack_require__(522); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -42838,7 +49798,7 @@ function isEnum(obj, key) { /***/ }), -/* 438 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42851,7 +49811,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(520); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -42859,7 +49819,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 439 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42872,7 +49832,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(440); +var isObject = __webpack_require__(521); function isObjectObject(o) { return isObject(o) === true @@ -42903,7 +49863,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 440 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42922,7 +49882,7 @@ module.exports = function isObject(val) { /***/ }), -/* 441 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42969,7 +49929,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 442 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42998,7 +49958,7 @@ function flat(arr, res) { /***/ }), -/* 443 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43012,10 +49972,10 @@ function flat(arr, res) { var util = __webpack_require__(29); -var isNumber = __webpack_require__(444); -var extend = __webpack_require__(430); -var repeat = __webpack_require__(446); -var toRegex = __webpack_require__(447); +var isNumber = __webpack_require__(525); +var extend = __webpack_require__(511); +var repeat = __webpack_require__(527); +var toRegex = __webpack_require__(528); /** * Return a range of numbers or letters. @@ -43213,7 +50173,7 @@ module.exports = fillRange; /***/ }), -/* 444 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43226,7 +50186,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(445); +var typeOf = __webpack_require__(526); module.exports = function isNumber(num) { var type = typeOf(num); @@ -43242,10 +50202,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 445 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(508); var toString = Object.prototype.toString; /** @@ -43364,7 +50324,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 446 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43441,7 +50401,7 @@ function repeat(str, num) { /***/ }), -/* 447 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43454,8 +50414,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(446); -var isNumber = __webpack_require__(444); +var repeat = __webpack_require__(527); +var isNumber = __webpack_require__(525); var cache = {}; function toRegexRange(min, max, options) { @@ -43742,7 +50702,7 @@ module.exports = toRegexRange; /***/ }), -/* 448 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43767,14 +50727,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 449 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(450); -var utils = __webpack_require__(435); +var Node = __webpack_require__(531); +var utils = __webpack_require__(516); /** * Braces parsers @@ -44134,15 +51094,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 450 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(440); -var define = __webpack_require__(451); -var utils = __webpack_require__(458); +var isObject = __webpack_require__(521); +var define = __webpack_require__(532); +var utils = __webpack_require__(539); var ownNames; /** @@ -44633,7 +51593,7 @@ exports = module.exports = Node; /***/ }), -/* 451 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44646,7 +51606,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(452); +var isDescriptor = __webpack_require__(533); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -44671,7 +51631,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 452 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44684,9 +51644,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(453); -var isAccessor = __webpack_require__(454); -var isData = __webpack_require__(456); +var typeOf = __webpack_require__(534); +var isAccessor = __webpack_require__(535); +var isData = __webpack_require__(537); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -44700,7 +51660,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 453 */ +/* 534 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -44835,7 +51795,7 @@ function isBuffer(val) { /***/ }), -/* 454 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44848,7 +51808,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(455); +var typeOf = __webpack_require__(536); // accessor descriptor properties var accessor = { @@ -44911,7 +51871,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 455 */ +/* 536 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -45046,7 +52006,7 @@ function isBuffer(val) { /***/ }), -/* 456 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45059,7 +52019,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(457); +var typeOf = __webpack_require__(538); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -45102,7 +52062,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 457 */ +/* 538 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -45237,13 +52197,13 @@ function isBuffer(val) { /***/ }), -/* 458 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(445); +var typeOf = __webpack_require__(526); var utils = module.exports; /** @@ -46263,17 +53223,17 @@ function assert(val, message) { /***/ }), -/* 459 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(430); -var Snapdragon = __webpack_require__(460); -var compilers = __webpack_require__(434); -var parsers = __webpack_require__(449); -var utils = __webpack_require__(435); +var extend = __webpack_require__(511); +var Snapdragon = __webpack_require__(541); +var compilers = __webpack_require__(515); +var parsers = __webpack_require__(530); +var utils = __webpack_require__(516); /** * Customize Snapdragon parser and renderer @@ -46374,17 +53334,17 @@ module.exports = Braces; /***/ }), -/* 460 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(461); -var define = __webpack_require__(422); -var Compiler = __webpack_require__(491); -var Parser = __webpack_require__(521); -var utils = __webpack_require__(501); +var Base = __webpack_require__(542); +var define = __webpack_require__(503); +var Compiler = __webpack_require__(571); +var Parser = __webpack_require__(601); +var utils = __webpack_require__(581); var regexCache = {}; var cache = {}; @@ -46555,20 +53515,20 @@ module.exports.Parser = Parser; /***/ }), -/* 461 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var define = __webpack_require__(462); -var CacheBase = __webpack_require__(463); -var Emitter = __webpack_require__(464); -var isObject = __webpack_require__(440); -var merge = __webpack_require__(482); -var pascal = __webpack_require__(485); -var cu = __webpack_require__(486); +var define = __webpack_require__(543); +var CacheBase = __webpack_require__(544); +var Emitter = __webpack_require__(545); +var isObject = __webpack_require__(521); +var merge = __webpack_require__(562); +var pascal = __webpack_require__(565); +var cu = __webpack_require__(566); /** * Optionally define a custom `cache` namespace to use. @@ -46997,7 +53957,7 @@ module.exports.namespace = namespace; /***/ }), -/* 462 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47010,7 +53970,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(452); +var isDescriptor = __webpack_require__(533); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -47035,21 +53995,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 463 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(440); -var Emitter = __webpack_require__(464); -var visit = __webpack_require__(465); -var toPath = __webpack_require__(468); -var union = __webpack_require__(469); -var del = __webpack_require__(473); -var get = __webpack_require__(471); -var has = __webpack_require__(478); -var set = __webpack_require__(481); +var isObject = __webpack_require__(521); +var Emitter = __webpack_require__(545); +var visit = __webpack_require__(546); +var toPath = __webpack_require__(549); +var union = __webpack_require__(550); +var del = __webpack_require__(554); +var get = __webpack_require__(552); +var has = __webpack_require__(559); +var set = __webpack_require__(553); /** * Create a `Cache` constructor that when instantiated will @@ -47303,7 +54263,7 @@ module.exports.namespace = namespace; /***/ }), -/* 464 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { @@ -47472,7 +54432,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 465 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47485,8 +54445,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(466); -var mapVisit = __webpack_require__(467); +var visit = __webpack_require__(547); +var mapVisit = __webpack_require__(548); module.exports = function(collection, method, val) { var result; @@ -47509,7 +54469,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 466 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47522,7 +54482,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(440); +var isObject = __webpack_require__(521); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -47549,14 +54509,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 467 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var visit = __webpack_require__(466); +var visit = __webpack_require__(547); /** * Map `visit` over an array of objects. @@ -47593,7 +54553,7 @@ function isObject(val) { /***/ }), -/* 468 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47606,7 +54566,7 @@ function isObject(val) { -var typeOf = __webpack_require__(445); +var typeOf = __webpack_require__(526); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -47633,16 +54593,16 @@ function filter(arr) { /***/ }), -/* 469 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(431); -var union = __webpack_require__(470); -var get = __webpack_require__(471); -var set = __webpack_require__(472); +var isObject = __webpack_require__(512); +var union = __webpack_require__(551); +var get = __webpack_require__(552); +var set = __webpack_require__(553); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -47670,7 +54630,7 @@ function arrayify(val) { /***/ }), -/* 470 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47706,7 +54666,7 @@ module.exports = function union(init) { /***/ }), -/* 471 */ +/* 552 */ /***/ (function(module, exports) { /*! @@ -47762,7 +54722,7 @@ function toString(val) { /***/ }), -/* 472 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47775,64 +54735,56 @@ function toString(val) { -var toPath = __webpack_require__(468); -var extend = __webpack_require__(430); -var isPlainObject = __webpack_require__(439); -var isObject = __webpack_require__(431); +var split = __webpack_require__(517); +var extend = __webpack_require__(511); +var isPlainObject = __webpack_require__(520); +var isObject = __webpack_require__(512); -module.exports = function(obj, path, val) { +module.exports = function(obj, prop, val) { if (!isObject(obj)) { return obj; } - if (Array.isArray(path)) { - path = toPath(path); + if (Array.isArray(prop)) { + prop = [].concat.apply([], prop).join('.'); } - if (typeof path !== 'string') { + if (typeof prop !== 'string') { return obj; } - var segs = path.split('.'); - var len = segs.length, i = -1; - var res = obj; - var last; - - while (++i < len) { - var key = segs[i]; - - while (key[key.length - 1] === '\\') { - key = key.slice(0, -1) + '.' + segs[++i]; - } - - if (i === len - 1) { - last = key; - break; - } + var keys = split(prop, {sep: '.', brackets: true}).filter(isValidKey); + var len = keys.length; + var idx = -1; + var current = obj; - if (!isObject(obj[key])) { - obj[key] = {}; + while (++idx < len) { + var key = keys[idx]; + if (idx !== len - 1) { + if (!isObject(current[key])) { + current[key] = {}; + } + current = current[key]; + continue; } - obj = obj[key]; - } - if (obj.hasOwnProperty(last) && isObject(obj[last])) { - if (isPlainObject(val)) { - extend(obj[last], val); + if (isPlainObject(current[key]) && isPlainObject(val)) { + current[key] = extend({}, current[key], val); } else { - obj[last] = val; + current[key] = val; } - - } else { - obj[last] = val; } - return res; + + return obj; }; +function isValidKey(key) { + return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'; +} /***/ }), -/* 473 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47845,8 +54797,8 @@ module.exports = function(obj, path, val) { -var isObject = __webpack_require__(440); -var has = __webpack_require__(474); +var isObject = __webpack_require__(521); +var has = __webpack_require__(555); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -47871,7 +54823,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 474 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47884,9 +54836,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(475); -var hasValues = __webpack_require__(477); -var get = __webpack_require__(471); +var isObject = __webpack_require__(556); +var hasValues = __webpack_require__(558); +var get = __webpack_require__(552); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -47897,7 +54849,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 475 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47910,7 +54862,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(476); +var isArray = __webpack_require__(557); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -47918,7 +54870,7 @@ module.exports = function isObject(val) { /***/ }), -/* 476 */ +/* 557 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -47929,7 +54881,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 477 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47972,7 +54924,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 478 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47985,9 +54937,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(440); -var hasValues = __webpack_require__(479); -var get = __webpack_require__(471); +var isObject = __webpack_require__(521); +var hasValues = __webpack_require__(560); +var get = __webpack_require__(552); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -47995,7 +54947,7 @@ module.exports = function(val, prop) { /***/ }), -/* 479 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48008,8 +54960,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(480); -var isNumber = __webpack_require__(444); +var typeOf = __webpack_require__(561); +var isNumber = __webpack_require__(525); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -48062,10 +55014,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 480 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(508); var toString = Object.prototype.toString; /** @@ -48187,72 +55139,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 481 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * set-value - * - * Copyright (c) 2014-2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var split = __webpack_require__(436); -var extend = __webpack_require__(430); -var isPlainObject = __webpack_require__(439); -var isObject = __webpack_require__(431); - -module.exports = function(obj, prop, val) { - if (!isObject(obj)) { - return obj; - } - - if (Array.isArray(prop)) { - prop = [].concat.apply([], prop).join('.'); - } - - if (typeof prop !== 'string') { - return obj; - } - - var keys = split(prop, {sep: '.', brackets: true}); - var len = keys.length; - var idx = -1; - var current = obj; - - while (++idx < len) { - var key = keys[idx]; - if (idx !== len - 1) { - if (!isObject(current[key])) { - current[key] = {}; - } - current = current[key]; - continue; - } - - if (isPlainObject(current[key]) && isPlainObject(val)) { - current[key] = extend({}, current[key], val); - } else { - current[key] = val; - } - } - - return obj; -}; - - -/***/ }), -/* 482 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(483); -var forIn = __webpack_require__(484); +var isExtendable = __webpack_require__(563); +var forIn = __webpack_require__(564); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -48316,7 +55210,7 @@ module.exports = mixinDeep; /***/ }), -/* 483 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48329,7 +55223,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(520); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -48337,7 +55231,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 484 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48360,7 +55254,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 485 */ +/* 565 */ /***/ (function(module, exports) { /*! @@ -48387,14 +55281,14 @@ module.exports = pascalcase; /***/ }), -/* 486 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var utils = __webpack_require__(487); +var utils = __webpack_require__(567); /** * Expose class utils @@ -48759,7 +55653,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 487 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48773,10 +55667,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(470); -utils.define = __webpack_require__(422); -utils.isObj = __webpack_require__(440); -utils.staticExtend = __webpack_require__(488); +utils.union = __webpack_require__(551); +utils.define = __webpack_require__(503); +utils.isObj = __webpack_require__(521); +utils.staticExtend = __webpack_require__(568); /** @@ -48787,7 +55681,7 @@ module.exports = utils; /***/ }), -/* 488 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48800,8 +55694,8 @@ module.exports = utils; -var copy = __webpack_require__(489); -var define = __webpack_require__(422); +var copy = __webpack_require__(569); +var define = __webpack_require__(503); var util = __webpack_require__(29); /** @@ -48884,15 +55778,15 @@ module.exports = extend; /***/ }), -/* 489 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(445); -var copyDescriptor = __webpack_require__(490); -var define = __webpack_require__(422); +var typeOf = __webpack_require__(526); +var copyDescriptor = __webpack_require__(570); +var define = __webpack_require__(503); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -49065,7 +55959,7 @@ module.exports.has = has; /***/ }), -/* 490 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49153,16 +56047,16 @@ function isObject(val) { /***/ }), -/* 491 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(492); -var define = __webpack_require__(422); -var debug = __webpack_require__(494)('snapdragon:compiler'); -var utils = __webpack_require__(501); +var use = __webpack_require__(572); +var define = __webpack_require__(503); +var debug = __webpack_require__(574)('snapdragon:compiler'); +var utils = __webpack_require__(581); /** * Create a new `Compiler` with the given `options`. @@ -49316,7 +56210,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(520); + var sourcemaps = __webpack_require__(600); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -49337,7 +56231,7 @@ module.exports = Compiler; /***/ }), -/* 492 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49350,7 +56244,7 @@ module.exports = Compiler; -var utils = __webpack_require__(493); +var utils = __webpack_require__(573); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -49465,7 +56359,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 493 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49479,8 +56373,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(422); -utils.isObject = __webpack_require__(440); +utils.define = __webpack_require__(503); +utils.isObject = __webpack_require__(521); utils.isString = function(val) { @@ -49495,7 +56389,7 @@ module.exports = utils; /***/ }), -/* 494 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -49504,14 +56398,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(495); + module.exports = __webpack_require__(575); } else { - module.exports = __webpack_require__(498); + module.exports = __webpack_require__(578); } /***/ }), -/* 495 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -49520,7 +56414,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(496); +exports = module.exports = __webpack_require__(576); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -49702,7 +56596,7 @@ function localstorage() { /***/ }), -/* 496 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { @@ -49718,7 +56612,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(497); +exports.humanize = __webpack_require__(577); /** * The currently active debug mode names, and names to skip. @@ -49910,7 +56804,7 @@ function coerce(val) { /***/ }), -/* 497 */ +/* 577 */ /***/ (function(module, exports) { /** @@ -50068,14 +56962,14 @@ function plural(ms, n, name) { /***/ }), -/* 498 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { /** * Module dependencies. */ -var tty = __webpack_require__(499); +var tty = __webpack_require__(579); var util = __webpack_require__(29); /** @@ -50084,7 +56978,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(496); +exports = module.exports = __webpack_require__(576); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -50263,7 +57157,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(500); + var net = __webpack_require__(580); stream = new net.Socket({ fd: fd, readable: false, @@ -50322,19 +57216,19 @@ exports.enable(load()); /***/ }), -/* 499 */ +/* 579 */ /***/ (function(module, exports) { module.exports = require("tty"); /***/ }), -/* 500 */ +/* 580 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 501 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50344,9 +57238,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(430); -exports.SourceMap = __webpack_require__(502); -exports.sourceMapResolve = __webpack_require__(513); +exports.extend = __webpack_require__(511); +exports.SourceMap = __webpack_require__(582); +exports.sourceMapResolve = __webpack_require__(593); /** * Convert backslash in the given string to forward slashes @@ -50389,7 +57283,7 @@ exports.last = function(arr, n) { /***/ }), -/* 502 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -50397,13 +57291,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(503).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(509).SourceMapConsumer; -exports.SourceNode = __webpack_require__(512).SourceNode; +exports.SourceMapGenerator = __webpack_require__(583).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(589).SourceMapConsumer; +exports.SourceNode = __webpack_require__(592).SourceNode; /***/ }), -/* 503 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50413,10 +57307,10 @@ exports.SourceNode = __webpack_require__(512).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(504); -var util = __webpack_require__(506); -var ArraySet = __webpack_require__(507).ArraySet; -var MappingList = __webpack_require__(508).MappingList; +var base64VLQ = __webpack_require__(584); +var util = __webpack_require__(586); +var ArraySet = __webpack_require__(587).ArraySet; +var MappingList = __webpack_require__(588).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -50825,7 +57719,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 504 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50865,7 +57759,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(505); +var base64 = __webpack_require__(585); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -50971,7 +57865,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 505 */ +/* 585 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51044,7 +57938,7 @@ exports.decode = function (charCode) { /***/ }), -/* 506 */ +/* 586 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51467,7 +58361,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 507 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51477,7 +58371,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(506); +var util = __webpack_require__(586); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -51594,7 +58488,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 508 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51604,7 +58498,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(506); +var util = __webpack_require__(586); /** * Determine whether mappingB is after mappingA with respect to generated @@ -51679,7 +58573,7 @@ exports.MappingList = MappingList; /***/ }), -/* 509 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51689,11 +58583,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(506); -var binarySearch = __webpack_require__(510); -var ArraySet = __webpack_require__(507).ArraySet; -var base64VLQ = __webpack_require__(504); -var quickSort = __webpack_require__(511).quickSort; +var util = __webpack_require__(586); +var binarySearch = __webpack_require__(590); +var ArraySet = __webpack_require__(587).ArraySet; +var base64VLQ = __webpack_require__(584); +var quickSort = __webpack_require__(591).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -52767,7 +59661,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 510 */ +/* 590 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52884,7 +59778,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 511 */ +/* 591 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -53004,7 +59898,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 512 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -53014,8 +59908,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(503).SourceMapGenerator; -var util = __webpack_require__(506); +var SourceMapGenerator = __webpack_require__(583).SourceMapGenerator; +var util = __webpack_require__(586); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -53423,17 +60317,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 513 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(514) -var resolveUrl = __webpack_require__(515) -var decodeUriComponent = __webpack_require__(516) -var urix = __webpack_require__(518) -var atob = __webpack_require__(519) +var sourceMappingURL = __webpack_require__(594) +var resolveUrl = __webpack_require__(595) +var decodeUriComponent = __webpack_require__(596) +var urix = __webpack_require__(598) +var atob = __webpack_require__(599) @@ -53731,7 +60625,7 @@ module.exports = { /***/ }), -/* 514 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -53794,7 +60688,7 @@ void (function(root, factory) { /***/ }), -/* 515 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -53812,13 +60706,13 @@ module.exports = resolveUrl /***/ }), -/* 516 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(517) +var decodeUriComponent = __webpack_require__(597) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -53829,7 +60723,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 517 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53930,7 +60824,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 518 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -53953,7 +60847,7 @@ module.exports = urix /***/ }), -/* 519 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53967,7 +60861,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 520 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53975,8 +60869,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(23); var path = __webpack_require__(16); -var define = __webpack_require__(422); -var utils = __webpack_require__(501); +var define = __webpack_require__(503); +var utils = __webpack_require__(581); /** * Expose `mixin()`. @@ -54119,19 +61013,19 @@ exports.comment = function(node) { /***/ }), -/* 521 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(492); +var use = __webpack_require__(572); var util = __webpack_require__(29); -var Cache = __webpack_require__(522); -var define = __webpack_require__(422); -var debug = __webpack_require__(494)('snapdragon:parser'); -var Position = __webpack_require__(523); -var utils = __webpack_require__(501); +var Cache = __webpack_require__(602); +var define = __webpack_require__(503); +var debug = __webpack_require__(574)('snapdragon:parser'); +var Position = __webpack_require__(603); +var utils = __webpack_require__(581); /** * Create a new `Parser` with the given `input` and `options`. @@ -54659,7 +61553,7 @@ module.exports = Parser; /***/ }), -/* 522 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54766,13 +61660,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 523 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(422); +var define = __webpack_require__(503); /** * Store position for a node @@ -54787,16 +61681,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 524 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(525); -var define = __webpack_require__(531); -var extend = __webpack_require__(532); -var not = __webpack_require__(534); +var safe = __webpack_require__(605); +var define = __webpack_require__(611); +var extend = __webpack_require__(612); +var not = __webpack_require__(614); var MAX_LENGTH = 1024 * 64; /** @@ -54949,10 +61843,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 525 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(526); +var parse = __webpack_require__(606); var types = parse.types; module.exports = function (re, opts) { @@ -54998,13 +61892,13 @@ function isRegExp (x) { /***/ }), -/* 526 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(527); -var types = __webpack_require__(528); -var sets = __webpack_require__(529); -var positions = __webpack_require__(530); +var util = __webpack_require__(607); +var types = __webpack_require__(608); +var sets = __webpack_require__(609); +var positions = __webpack_require__(610); module.exports = function(regexpStr) { @@ -55286,11 +62180,11 @@ module.exports.types = types; /***/ }), -/* 527 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); -var sets = __webpack_require__(529); +var types = __webpack_require__(608); +var sets = __webpack_require__(609); // All of these are private and only used by randexp. @@ -55403,7 +62297,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 528 */ +/* 608 */ /***/ (function(module, exports) { module.exports = { @@ -55419,10 +62313,10 @@ module.exports = { /***/ }), -/* 529 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); +var types = __webpack_require__(608); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -55507,10 +62401,10 @@ exports.anyChar = function() { /***/ }), -/* 530 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); +var types = __webpack_require__(608); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -55530,7 +62424,7 @@ exports.end = function() { /***/ }), -/* 531 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55543,8 +62437,8 @@ exports.end = function() { -var isobject = __webpack_require__(440); -var isDescriptor = __webpack_require__(452); +var isobject = __webpack_require__(521); +var isDescriptor = __webpack_require__(533); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -55575,14 +62469,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 532 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(533); -var assignSymbols = __webpack_require__(441); +var isExtendable = __webpack_require__(613); +var assignSymbols = __webpack_require__(522); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -55642,7 +62536,7 @@ function isEnum(obj, key) { /***/ }), -/* 533 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55655,7 +62549,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(520); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -55663,14 +62557,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 534 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(532); -var safe = __webpack_require__(525); +var extend = __webpack_require__(612); +var safe = __webpack_require__(605); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -55742,14 +62636,14 @@ module.exports = toRegex; /***/ }), -/* 535 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(536); -var extglob = __webpack_require__(551); +var nanomatch = __webpack_require__(616); +var extglob = __webpack_require__(631); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -55826,7 +62720,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 536 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55837,17 +62731,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(29); -var toRegex = __webpack_require__(421); -var extend = __webpack_require__(537); +var toRegex = __webpack_require__(502); +var extend = __webpack_require__(617); /** * Local dependencies */ -var compilers = __webpack_require__(539); -var parsers = __webpack_require__(540); -var cache = __webpack_require__(543); -var utils = __webpack_require__(545); +var compilers = __webpack_require__(619); +var parsers = __webpack_require__(620); +var cache = __webpack_require__(623); +var utils = __webpack_require__(625); var MAX_LENGTH = 1024 * 64; /** @@ -56671,14 +63565,14 @@ module.exports = nanomatch; /***/ }), -/* 537 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(538); -var assignSymbols = __webpack_require__(441); +var isExtendable = __webpack_require__(618); +var assignSymbols = __webpack_require__(522); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -56738,7 +63632,7 @@ function isEnum(obj, key) { /***/ }), -/* 538 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56751,7 +63645,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(520); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -56759,7 +63653,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 539 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57105,15 +63999,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 540 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(432); -var toRegex = __webpack_require__(421); -var isOdd = __webpack_require__(541); +var regexNot = __webpack_require__(513); +var toRegex = __webpack_require__(502); +var isOdd = __webpack_require__(621); /** * Characters to use in negation regex (we want to "not" match @@ -57499,7 +64393,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 541 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57512,7 +64406,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(542); +var isNumber = __webpack_require__(622); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -57526,7 +64420,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 542 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57554,14 +64448,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 543 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(544))(); +module.exports = new (__webpack_require__(624))(); /***/ }), -/* 544 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57574,7 +64468,7 @@ module.exports = new (__webpack_require__(544))(); -var MapCache = __webpack_require__(522); +var MapCache = __webpack_require__(602); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -57696,7 +64590,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 545 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57709,14 +64603,14 @@ var path = __webpack_require__(16); * Module dependencies */ -var isWindows = __webpack_require__(546)(); -var Snapdragon = __webpack_require__(460); -utils.define = __webpack_require__(547); -utils.diff = __webpack_require__(548); -utils.extend = __webpack_require__(537); -utils.pick = __webpack_require__(549); -utils.typeOf = __webpack_require__(550); -utils.unique = __webpack_require__(433); +var isWindows = __webpack_require__(626)(); +var Snapdragon = __webpack_require__(541); +utils.define = __webpack_require__(627); +utils.diff = __webpack_require__(628); +utils.extend = __webpack_require__(617); +utils.pick = __webpack_require__(629); +utils.typeOf = __webpack_require__(630); +utils.unique = __webpack_require__(514); /** * Returns true if the given value is effectively an empty string @@ -58082,7 +64976,7 @@ utils.unixify = function(options) { /***/ }), -/* 546 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -58110,7 +65004,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 547 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58123,8 +65017,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(440); -var isDescriptor = __webpack_require__(452); +var isobject = __webpack_require__(521); +var isDescriptor = __webpack_require__(533); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -58155,7 +65049,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 548 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58209,7 +65103,7 @@ function diffArray(one, two) { /***/ }), -/* 549 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58222,7 +65116,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(440); +var isObject = __webpack_require__(521); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -58251,7 +65145,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 550 */ +/* 630 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -58386,7 +65280,7 @@ function isBuffer(val) { /***/ }), -/* 551 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58396,18 +65290,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(430); -var unique = __webpack_require__(433); -var toRegex = __webpack_require__(421); +var extend = __webpack_require__(511); +var unique = __webpack_require__(514); +var toRegex = __webpack_require__(502); /** * Local dependencies */ -var compilers = __webpack_require__(552); -var parsers = __webpack_require__(558); -var Extglob = __webpack_require__(561); -var utils = __webpack_require__(560); +var compilers = __webpack_require__(632); +var parsers = __webpack_require__(638); +var Extglob = __webpack_require__(641); +var utils = __webpack_require__(640); var MAX_LENGTH = 1024 * 64; /** @@ -58724,13 +65618,13 @@ module.exports = extglob; /***/ }), -/* 552 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(553); +var brackets = __webpack_require__(633); /** * Extglob compilers @@ -58900,7 +65794,7 @@ module.exports = function(extglob) { /***/ }), -/* 553 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58910,17 +65804,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(554); -var parsers = __webpack_require__(556); +var compilers = __webpack_require__(634); +var parsers = __webpack_require__(636); /** * Module dependencies */ -var debug = __webpack_require__(494)('expand-brackets'); -var extend = __webpack_require__(430); -var Snapdragon = __webpack_require__(460); -var toRegex = __webpack_require__(421); +var debug = __webpack_require__(574)('expand-brackets'); +var extend = __webpack_require__(511); +var Snapdragon = __webpack_require__(541); +var toRegex = __webpack_require__(502); /** * Parses the given POSIX character class `pattern` and returns a @@ -59118,13 +66012,13 @@ module.exports = brackets; /***/ }), -/* 554 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(555); +var posix = __webpack_require__(635); module.exports = function(brackets) { brackets.compiler @@ -59212,7 +66106,7 @@ module.exports = function(brackets) { /***/ }), -/* 555 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59241,14 +66135,14 @@ module.exports = { /***/ }), -/* 556 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(557); -var define = __webpack_require__(422); +var utils = __webpack_require__(637); +var define = __webpack_require__(503); /** * Text regex @@ -59467,14 +66361,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 557 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(421); -var regexNot = __webpack_require__(432); +var toRegex = __webpack_require__(502); +var regexNot = __webpack_require__(513); var cached; /** @@ -59508,15 +66402,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 558 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(553); -var define = __webpack_require__(559); -var utils = __webpack_require__(560); +var brackets = __webpack_require__(633); +var define = __webpack_require__(639); +var utils = __webpack_require__(640); /** * Characters to use in text regex (we want to "not" match @@ -59671,7 +66565,7 @@ module.exports = parsers; /***/ }), -/* 559 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59684,7 +66578,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(452); +var isDescriptor = __webpack_require__(533); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -59709,14 +66603,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 560 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(432); -var Cache = __webpack_require__(544); +var regex = __webpack_require__(513); +var Cache = __webpack_require__(624); /** * Utils @@ -59785,7 +66679,7 @@ utils.createRegex = function(str) { /***/ }), -/* 561 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59795,16 +66689,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(460); -var define = __webpack_require__(559); -var extend = __webpack_require__(430); +var Snapdragon = __webpack_require__(541); +var define = __webpack_require__(639); +var extend = __webpack_require__(511); /** * Local dependencies */ -var compilers = __webpack_require__(552); -var parsers = __webpack_require__(558); +var compilers = __webpack_require__(632); +var parsers = __webpack_require__(638); /** * Customize Snapdragon parser and renderer @@ -59870,16 +66764,16 @@ module.exports = Extglob; /***/ }), -/* 562 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(551); -var nanomatch = __webpack_require__(536); -var regexNot = __webpack_require__(432); -var toRegex = __webpack_require__(524); +var extglob = __webpack_require__(631); +var nanomatch = __webpack_require__(616); +var regexNot = __webpack_require__(513); +var toRegex = __webpack_require__(604); var not; /** @@ -59960,14 +66854,14 @@ function textRegex(pattern) { /***/ }), -/* 563 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(544))(); +module.exports = new (__webpack_require__(624))(); /***/ }), -/* 564 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59980,13 +66874,13 @@ var path = __webpack_require__(16); * Module dependencies */ -var Snapdragon = __webpack_require__(460); -utils.define = __webpack_require__(531); -utils.diff = __webpack_require__(548); -utils.extend = __webpack_require__(532); -utils.pick = __webpack_require__(549); -utils.typeOf = __webpack_require__(565); -utils.unique = __webpack_require__(433); +var Snapdragon = __webpack_require__(541); +utils.define = __webpack_require__(611); +utils.diff = __webpack_require__(628); +utils.extend = __webpack_require__(612); +utils.pick = __webpack_require__(629); +utils.typeOf = __webpack_require__(645); +utils.unique = __webpack_require__(514); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -60283,7 +67177,7 @@ utils.unixify = function(options) { /***/ }), -/* 565 */ +/* 645 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -60418,7 +67312,7 @@ function isBuffer(val) { /***/ }), -/* 566 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60437,9 +67331,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(567); -var reader_1 = __webpack_require__(580); -var fs_stream_1 = __webpack_require__(584); +var readdir = __webpack_require__(647); +var reader_1 = __webpack_require__(660); +var fs_stream_1 = __webpack_require__(664); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -60500,15 +67394,15 @@ exports.default = ReaderAsync; /***/ }), -/* 567 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(568); -const readdirAsync = __webpack_require__(576); -const readdirStream = __webpack_require__(579); +const readdirSync = __webpack_require__(648); +const readdirAsync = __webpack_require__(656); +const readdirStream = __webpack_require__(659); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -60592,7 +67486,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 568 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60600,11 +67494,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(569); +const DirectoryReader = __webpack_require__(649); let syncFacade = { - fs: __webpack_require__(574), - forEach: __webpack_require__(575), + fs: __webpack_require__(654), + forEach: __webpack_require__(655), sync: true }; @@ -60633,7 +67527,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 569 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60642,9 +67536,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(28).Readable; const EventEmitter = __webpack_require__(46).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(570); -const stat = __webpack_require__(572); -const call = __webpack_require__(573); +const normalizeOptions = __webpack_require__(650); +const stat = __webpack_require__(652); +const call = __webpack_require__(653); /** * Asynchronously reads the contents of a directory and streams the results @@ -61020,14 +67914,14 @@ module.exports = DirectoryReader; /***/ }), -/* 570 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(571); +const globToRegExp = __webpack_require__(651); module.exports = normalizeOptions; @@ -61204,7 +68098,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 571 */ +/* 651 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -61341,13 +68235,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 572 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(573); +const call = __webpack_require__(653); module.exports = stat; @@ -61422,7 +68316,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 573 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61483,14 +68377,14 @@ function callOnce (fn) { /***/ }), -/* 574 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(573); +const call = __webpack_require__(653); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -61554,7 +68448,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 575 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61583,7 +68477,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 576 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61591,12 +68485,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(577); -const DirectoryReader = __webpack_require__(569); +const maybe = __webpack_require__(657); +const DirectoryReader = __webpack_require__(649); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(578), + forEach: __webpack_require__(658), async: true }; @@ -61638,7 +68532,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 577 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61665,7 +68559,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 578 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61701,7 +68595,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 579 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61709,11 +68603,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(569); +const DirectoryReader = __webpack_require__(649); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(578), + forEach: __webpack_require__(658), async: true }; @@ -61733,16 +68627,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 580 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(581); -var entry_1 = __webpack_require__(583); -var pathUtil = __webpack_require__(582); +var deep_1 = __webpack_require__(661); +var entry_1 = __webpack_require__(663); +var pathUtil = __webpack_require__(662); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -61808,14 +68702,14 @@ exports.default = Reader; /***/ }), -/* 581 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(582); -var patternUtils = __webpack_require__(413); +var pathUtils = __webpack_require__(662); +var patternUtils = __webpack_require__(495); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -61898,7 +68792,7 @@ exports.default = DeepFilter; /***/ }), -/* 582 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61929,14 +68823,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 583 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(582); -var patternUtils = __webpack_require__(413); +var pathUtils = __webpack_require__(662); +var patternUtils = __webpack_require__(495); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -62021,7 +68915,7 @@ exports.default = EntryFilter; /***/ }), -/* 584 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62041,8 +68935,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var fsStat = __webpack_require__(585); -var fs_1 = __webpack_require__(589); +var fsStat = __webpack_require__(665); +var fs_1 = __webpack_require__(669); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -62092,14 +68986,14 @@ exports.default = FileSystemStream; /***/ }), -/* 585 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(586); -const statProvider = __webpack_require__(588); +const optionsManager = __webpack_require__(666); +const statProvider = __webpack_require__(668); /** * Asynchronous API. */ @@ -62130,13 +69024,13 @@ exports.statSync = statSync; /***/ }), -/* 586 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(587); +const fsAdapter = __webpack_require__(667); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -62149,7 +69043,7 @@ exports.prepare = prepare; /***/ }), -/* 587 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62172,7 +69066,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 588 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62224,7 +69118,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 589 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62255,7 +69149,7 @@ exports.default = FileSystem; /***/ }), -/* 590 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62275,9 +69169,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var readdir = __webpack_require__(567); -var reader_1 = __webpack_require__(580); -var fs_stream_1 = __webpack_require__(584); +var readdir = __webpack_require__(647); +var reader_1 = __webpack_require__(660); +var fs_stream_1 = __webpack_require__(664); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -62345,7 +69239,7 @@ exports.default = ReaderStream; /***/ }), -/* 591 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62364,9 +69258,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(567); -var reader_1 = __webpack_require__(580); -var fs_sync_1 = __webpack_require__(592); +var readdir = __webpack_require__(647); +var reader_1 = __webpack_require__(660); +var fs_sync_1 = __webpack_require__(672); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -62426,7 +69320,7 @@ exports.default = ReaderSync; /***/ }), -/* 592 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62445,8 +69339,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(585); -var fs_1 = __webpack_require__(589); +var fsStat = __webpack_require__(665); +var fs_1 = __webpack_require__(669); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -62492,7 +69386,7 @@ exports.default = FileSystemSync; /***/ }), -/* 593 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62508,13 +69402,13 @@ exports.flatten = flatten; /***/ }), -/* 594 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(595); +var merge2 = __webpack_require__(177); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ @@ -62529,127 +69423,13 @@ exports.merge = merge; /***/ }), -/* 595 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/* - * merge2 - * https://github.com/teambition/merge2 - * - * Copyright (c) 2014-2016 Teambition - * Licensed under the MIT license. - */ -const Stream = __webpack_require__(28) -const PassThrough = Stream.PassThrough -const slice = Array.prototype.slice - -module.exports = merge2 - -function merge2 () { - const streamsQueue = [] - let merging = false - const args = slice.call(arguments) - let options = args[args.length - 1] - - if (options && !Array.isArray(options) && options.pipe == null) args.pop() - else options = {} - - const doEnd = options.end !== false - if (options.objectMode == null) options.objectMode = true - if (options.highWaterMark == null) options.highWaterMark = 64 * 1024 - const mergedStream = PassThrough(options) - - function addStream () { - for (let i = 0, len = arguments.length; i < len; i++) { - streamsQueue.push(pauseStreams(arguments[i], options)) - } - mergeStream() - return this - } - - function mergeStream () { - if (merging) return - merging = true - - let streams = streamsQueue.shift() - if (!streams) { - process.nextTick(endStream) - return - } - if (!Array.isArray(streams)) streams = [streams] - - let pipesCount = streams.length + 1 - - function next () { - if (--pipesCount > 0) return - merging = false - mergeStream() - } - - function pipe (stream) { - function onend () { - stream.removeListener('merge2UnpipeEnd', onend) - stream.removeListener('end', onend) - next() - } - // skip ended stream - if (stream._readableState.endEmitted) return next() - - stream.on('merge2UnpipeEnd', onend) - stream.on('end', onend) - stream.pipe(mergedStream, { end: false }) - // compatible for old stream - stream.resume() - } - - for (let i = 0; i < streams.length; i++) pipe(streams[i]) - - next() - } - - function endStream () { - merging = false - // emit 'queueDrain' when all streams merged. - mergedStream.emit('queueDrain') - return doEnd && mergedStream.end() - } - - mergedStream.setMaxListeners(0) - mergedStream.add = addStream - mergedStream.on('unpipe', function (stream) { - stream.emit('merge2UnpipeEnd') - }) - - if (args.length) addStream.apply(null, args) - return mergedStream -} - -// check and pause streams for pipe. -function pauseStreams (streams, options) { - if (!Array.isArray(streams)) { - // Backwards-compat with old-style streams - if (!streams._readableState && streams.pipe) streams = streams.pipe(PassThrough(options)) - if (!streams._readableState || !streams.pause || !streams.pipe) { - throw new Error('Only readable stream can be merged.') - } - streams.pause() - } else { - for (let i = 0, len = streams.length; i < len; i++) streams[i] = pauseStreams(streams[i], options) - } - return streams -} - - -/***/ }), -/* 596 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(597); +const pathType = __webpack_require__(676); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -62715,13 +69495,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 597 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(598); +const pify = __webpack_require__(677); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -62764,7 +69544,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 598 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62855,17 +69635,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 599 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); const path = __webpack_require__(16); -const fastGlob = __webpack_require__(409); -const gitIgnore = __webpack_require__(600); -const pify = __webpack_require__(601); -const slash = __webpack_require__(602); +const fastGlob = __webpack_require__(491); +const gitIgnore = __webpack_require__(679); +const pify = __webpack_require__(680); +const slash = __webpack_require__(681); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -62963,7 +69743,7 @@ module.exports.sync = options => { /***/ }), -/* 600 */ +/* 679 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -63432,7 +70212,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 601 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63507,7 +70287,7 @@ module.exports = (input, options) => { /***/ }), -/* 602 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63525,17 +70305,17 @@ module.exports = input => { /***/ }), -/* 603 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(604); -const CpFileError = __webpack_require__(606); -const fs = __webpack_require__(608); -const ProgressEmitter = __webpack_require__(610); +const {Buffer} = __webpack_require__(683); +const CpFileError = __webpack_require__(685); +const fs = __webpack_require__(687); +const ProgressEmitter = __webpack_require__(689); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -63689,11 +70469,11 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 604 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ -var buffer = __webpack_require__(605) +var buffer = __webpack_require__(684) var Buffer = buffer.Buffer // alternative to using Object.keys for old browsers @@ -63757,18 +70537,18 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 605 */ +/* 684 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 606 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(607); +const NestedError = __webpack_require__(686); class CpFileError extends NestedError { constructor(message, nested) { @@ -63782,7 +70562,7 @@ module.exports = CpFileError; /***/ }), -/* 607 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(44); @@ -63836,15 +70616,15 @@ module.exports = NestedError; /***/ }), -/* 608 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(115); -const pify = __webpack_require__(609); -const CpFileError = __webpack_require__(606); +const pify = __webpack_require__(688); +const CpFileError = __webpack_require__(685); const fsP = pify(fs); @@ -63989,7 +70769,7 @@ if (fs.copyFileSync) { /***/ }), -/* 609 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64064,7 +70844,7 @@ module.exports = (input, options) => { /***/ }), -/* 610 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64105,12 +70885,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 611 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(612); +const NestedError = __webpack_require__(691); class CpyError extends NestedError { constructor(message, nested) { @@ -64124,7 +70904,7 @@ module.exports = CpyError; /***/ }), -/* 612 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -64180,7 +70960,7 @@ module.exports = NestedError; /***/ }), -/* 613 */ +/* 692 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 8480e74ceb3a20..34a56615ed43a1 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -18,7 +18,6 @@ "@types/cmd-shim": "^2.0.0", "@types/cpy": "^5.1.0", "@types/dedent": "^0.7.0", - "@types/execa": "^0.9.0", "@types/getopts": "^2.0.1", "@types/glob": "^5.0.35", "@types/globby": "^6.1.0", @@ -40,8 +39,8 @@ "cmd-shim": "^2.1.0", "cpy": "^7.3.0", "dedent": "^0.7.0", - "del": "^4.1.1", - "execa": "^1.0.0", + "del": "^5.1.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "glob": "^7.1.2", "globby": "^8.0.1", @@ -53,7 +52,7 @@ "ora": "^1.4.0", "prettier": "^1.18.2", "read-pkg": "^5.2.0", - "rxjs": "^6.2.1", + "rxjs": "^6.5.3", "spawn-sync": "^1.0.15", "string-replace-loader": "^2.2.0", "strip-ansi": "^4.0.0", diff --git a/packages/kbn-pm/src/utils/child_process.ts b/packages/kbn-pm/src/utils/child_process.ts index 9b4b7b2516f1c7..784446924a8dc4 100644 --- a/packages/kbn-pm/src/utils/child_process.ts +++ b/packages/kbn-pm/src/utils/child_process.ts @@ -34,6 +34,7 @@ function generateColors() { export function spawn(command: string, args: string[], opts: execa.Options) { return execa(command, args, { stdio: 'inherit', + preferLocal: true, ...opts, }); } @@ -48,6 +49,7 @@ export function spawnStreaming( ) { const spawned = execa(command, args, { stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true, ...opts, }); diff --git a/packages/kbn-pm/src/utils/projects.test.ts b/packages/kbn-pm/src/utils/projects.test.ts index 1b839440b94499..093f178f1813a1 100644 --- a/packages/kbn-pm/src/utils/projects.test.ts +++ b/packages/kbn-pm/src/utils/projects.test.ts @@ -19,7 +19,7 @@ import { mkdir, symlink } from 'fs'; import { join, resolve } from 'path'; -import rmdir from 'rimraf'; +import del from 'del'; import { promisify } from 'util'; import { getProjectPaths } from '../config'; @@ -33,20 +33,20 @@ import { topologicallyBatchProjects, } from './projects'; -const rootPath = resolve(`${__dirname}/__fixtures__/kibana`); +const rootPath = resolve(__dirname, '__fixtures__/kibana'); const rootPlugins = join(rootPath, 'plugins'); describe('#getProjects', () => { beforeAll(async () => { await promisify(mkdir)(rootPlugins); - return promisify(symlink)( + await promisify(symlink)( join(__dirname, '__fixtures__/symlinked-plugins/corge'), join(rootPlugins, 'corge') ); }); - afterAll(() => promisify(rmdir)(rootPlugins)); + afterAll(async () => await del(rootPlugins)); test('find all packages in the packages directory', async () => { const projects = await getProjects(rootPath, ['packages/*']); diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 86a81207a9fa94..8c5358c82208df 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -20,12 +20,12 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "glob": "^7.1.2", "parse-link-header": "^1.0.1", "strip-ansi": "^5.2.0", - "rxjs": "^6.2.1", + "rxjs": "^6.5.3", "tar-fs": "^1.16.3", "tmp": "^0.1.0", "xml2js": "^0.4.22", diff --git a/packages/kbn-test/src/functional_tests/tasks.js b/packages/kbn-test/src/functional_tests/tasks.js index 6ebc49679fbf3b..d50f6a15c2e0b8 100644 --- a/packages/kbn-test/src/functional_tests/tasks.js +++ b/packages/kbn-test/src/functional_tests/tasks.js @@ -82,12 +82,20 @@ export async function runTests(options) { await withProcRunner(log, async procs => { const config = await readConfigFile(log, configPath); - const es = await runElasticsearch({ config, options: opts }); - await runKibanaServer({ procs, config, options: opts }); - await runFtr({ configPath, options: opts }); - - await procs.stop('kibana'); - await es.cleanup(); + let es; + try { + es = await runElasticsearch({ config, options: opts }); + await runKibanaServer({ procs, config, options: opts }); + await runFtr({ configPath, options: opts }); + } finally { + try { + await procs.stop('kibana'); + } finally { + if (es) { + await es.cleanup(); + } + } + } }); } } diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index cb4d2e3cadf956..d034e4393f58ff 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -52,7 +52,7 @@ "imports-loader": "^0.8.0", "jquery": "^3.4.1", "keymirror": "0.1.1", - "moment": "^2.20.1", + "moment": "^2.24.0", "node-sass": "^4.9.4", "postcss": "^7.0.5", "postcss-loader": "^3.0.0", diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index b665fa20ff1ff1..a79d08677020b5 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -10,12 +10,13 @@ "kbn:bootstrap": "tsc", "kbn:watch": "tsc --watch", "test": "tsd", - "clean": "rimraf target" + "clean": "del target" }, "dependencies": { "utility-types": "^3.7.0" }, "devDependencies": { + "del-cli": "^3.0.0", "tsd": "^0.7.4" } } diff --git a/renovate.json5 b/renovate.json5 index 21af4ad1e83f8d..0c288bb85c72c8 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -353,14 +353,6 @@ '@types/elasticsearch', ], }, - { - groupSlug: 'execa', - groupName: 'execa related packages', - packageNames: [ - 'execa', - '@types/execa', - ], - }, { groupSlug: 'fetch-mock', groupName: 'fetch-mock related packages', @@ -409,14 +401,6 @@ '@types/history', ], }, - { - groupSlug: 'humps', - groupName: 'humps related packages', - packageNames: [ - 'humps', - '@types/humps', - ], - }, { groupSlug: 'jquery', groupName: 'jquery related packages', @@ -537,14 +521,6 @@ '@types/request', ], }, - { - groupSlug: 'rimraf', - groupName: 'rimraf related packages', - packageNames: [ - 'rimraf', - '@types/rimraf', - ], - }, { groupSlug: 'selenium-webdriver', groupName: 'selenium-webdriver related packages', @@ -593,6 +569,14 @@ '@types/supertest', ], }, + { + groupSlug: 'supertest-as-promised', + groupName: 'supertest-as-promised related packages', + packageNames: [ + 'supertest-as-promised', + '@types/supertest-as-promised', + ], + }, { groupSlug: 'type-detect', groupName: 'type-detect related packages', diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js index 8d56f6406ef59b..a67593c02a5935 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.js @@ -113,7 +113,7 @@ export default class ClusterManager { ...scanDirs, ]; - const extraIgnores = scanDirs + const pluginInternalDirsIgnore = scanDirs .map(scanDir => resolve(scanDir, '*')) .concat(pluginPaths) .reduce( @@ -124,16 +124,11 @@ export default class ClusterManager { resolve(path, 'target'), resolve(path, 'scripts'), resolve(path, 'docs'), - resolve(path, 'src/legacy/server/sass/__tmp__'), - resolve(path, 'legacy/plugins/reporting/.chromium'), - resolve(path, 'legacy/plugins/siem/cypress'), - resolve(path, 'legacy/plugins/apm/cypress'), - resolve(path, 'x-pack/legacy/plugins/canvas/canvas_plugin_src') // prevents server from restarting twice for Canvas plugin changes ), [] ); - this.setupWatching(extraPaths, extraIgnores); + this.setupWatching(extraPaths, pluginInternalDirsIgnore); } else this.startCluster(); } @@ -170,7 +165,7 @@ export default class ClusterManager { .then(() => opn(openUrl)); } - setupWatching(extraPaths, extraIgnores) { + setupWatching(extraPaths, pluginInternalDirsIgnore) { const chokidar = require('chokidar'); const { fromRoot } = require('../../legacy/utils'); @@ -187,12 +182,21 @@ export default class ClusterManager { ...extraPaths, ].map(path => resolve(path)); + const ignorePaths = [ + fromRoot('src/legacy/server/sass/__tmp__'), + fromRoot('x-pack/legacy/plugins/reporting/.chromium'), + fromRoot('x-pack/legacy/plugins/siem/cypress'), + fromRoot('x-pack/legacy/plugins/apm/cypress'), + fromRoot('x-pack/legacy/plugins/canvas/canvas_plugin_src') // prevents server from restarting twice for Canvas plugin changes + ]; + this.watcher = chokidar.watch(uniq(watchPaths), { cwd: fromRoot('.'), ignored: [ /[\\\/](\..*|node_modules|bower_components|public|__[a-z0-9_]+__|coverage)[\\\/]/, /\.test\.(js|ts)$/, - ...extraIgnores, + ...pluginInternalDirsIgnore, + ...ignorePaths, 'plugins/java_languageserver' ], }); diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.js b/src/cli/serve/integration_tests/reload_logging_config.test.js index 8e8bdb15abc684..8558a5eb6c9103 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.js +++ b/src/cli/serve/integration_tests/reload_logging_config.test.js @@ -21,7 +21,7 @@ import { spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import rimraf from 'rimraf'; +import del from 'del'; import { safeDump } from 'js-yaml'; import { createMapStream, createSplitStream, createPromiseFromStreams } from '../../../legacy/utils/streams'; @@ -70,7 +70,7 @@ describe('Server logging configuration', function () { child = undefined; } - rimraf.sync(tempDir); + del.sync(tempDir, { force: true }); }); const isWindows = /^win/.test(process.platform); diff --git a/src/cli_plugin/install/cleanup.js b/src/cli_plugin/install/cleanup.js index 631257fba72716..55251e6a0a0db2 100644 --- a/src/cli_plugin/install/cleanup.js +++ b/src/cli_plugin/install/cleanup.js @@ -17,7 +17,7 @@ * under the License. */ -import rimraf from 'rimraf'; +import del from 'del'; import fs from 'fs'; export function cleanPrevious(settings, logger) { @@ -27,7 +27,7 @@ export function cleanPrevious(settings, logger) { logger.log('Found previous install attempt. Deleting...'); try { - rimraf.sync(settings.workingPath); + del.sync(settings.workingPath); } catch (e) { reject(e); } @@ -44,8 +44,8 @@ export function cleanArtifacts(settings) { // delete the working directory. // At this point we're bailing, so swallow any errors on delete. try { - rimraf.sync(settings.workingPath); - rimraf.sync(settings.plugins[0].path); + del.sync(settings.workingPath); + del.sync(settings.plugins[0].path); } catch (e) {} // eslint-disable-line no-empty } diff --git a/src/cli_plugin/install/cleanup.test.js b/src/cli_plugin/install/cleanup.test.js index a0f5f5064102ce..bb2771920b39cb 100644 --- a/src/cli_plugin/install/cleanup.test.js +++ b/src/cli_plugin/install/cleanup.test.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import fs from 'fs'; -import rimraf from 'rimraf'; +import del from 'del'; import { cleanPrevious, cleanArtifacts } from './cleanup'; import Logger from '../lib/logger'; @@ -48,11 +48,11 @@ describe('kibana cli', function () { logger.log.restore(); logger.error.restore(); fs.statSync.restore(); - rimraf.sync.restore(); + del.sync.restore(); }); it('should resolve if the working path does not exist', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync').callsFake(() => { const error = new Error('ENOENT'); error.code = 'ENOENT'; @@ -67,7 +67,7 @@ describe('kibana cli', function () { }); it('should rethrow any exception except ENOENT from fs.statSync', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync').throws(new Error('An Unhandled Error')); errorStub = sinon.stub(); @@ -79,7 +79,7 @@ describe('kibana cli', function () { }); it('should log a message if there was a working directory', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync'); return cleanPrevious(settings, logger) @@ -89,9 +89,9 @@ describe('kibana cli', function () { }); }); - it('should rethrow any exception from rimraf.sync', function () { + it('should rethrow any exception from del.sync', function () { sinon.stub(fs, 'statSync'); - sinon.stub(rimraf, 'sync').throws(new Error('I am an error thrown by rimraf')); + sinon.stub(del, 'sync').throws(new Error('I am an error thrown by del')); errorStub = sinon.stub(); return cleanPrevious(settings, logger) @@ -102,7 +102,7 @@ describe('kibana cli', function () { }); it('should resolve if the working path is deleted', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync'); return cleanPrevious(settings, logger) @@ -117,18 +117,18 @@ describe('kibana cli', function () { beforeEach(function () {}); afterEach(function () { - rimraf.sync.restore(); + del.sync.restore(); }); it('should attempt to delete the working directory', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); cleanArtifacts(settings); - expect(rimraf.sync.calledWith(settings.workingPath)).toBe(true); + expect(del.sync.calledWith(settings.workingPath)).toBe(true); }); - it('should swallow any errors thrown by rimraf.sync', function () { - sinon.stub(rimraf, 'sync').throws(new Error('Something bad happened.')); + it('should swallow any errors thrown by del.sync', function () { + sinon.stub(del, 'sync').throws(new Error('Something bad happened.')); expect(() => cleanArtifacts(settings)).not.toThrow(); }); diff --git a/src/cli_plugin/install/download.test.js b/src/cli_plugin/install/download.test.js index 9de6491f3c9571..0769e5351a5d28 100644 --- a/src/cli_plugin/install/download.test.js +++ b/src/cli_plugin/install/download.test.js @@ -20,7 +20,7 @@ import sinon from 'sinon'; import nock from 'nock'; import glob from 'glob-all'; -import rimraf from 'rimraf'; +import del from 'del'; import Fs from 'fs'; import Logger from '../lib/logger'; import { UnsupportedProtocolError } from '../lib/errors'; @@ -63,14 +63,14 @@ describe('kibana cli', function () { beforeEach(function () { sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); Fs.mkdirSync(testWorkingPath, { recursive: true }); }); afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); }); describe('_downloadSingle', function () { diff --git a/src/cli_plugin/install/install.js b/src/cli_plugin/install/install.js index 10816591f7d714..a62d8525f24952 100644 --- a/src/cli_plugin/install/install.js +++ b/src/cli_plugin/install/install.js @@ -25,7 +25,7 @@ import path from 'path'; import { cleanPrevious, cleanArtifacts } from './cleanup'; import { extract, getPackData } from './pack'; import { renamePlugin } from './rename'; -import { sync as rimrafSync } from 'rimraf'; +import del from 'del'; import { errorIfXPackInstall } from '../lib/error_if_x_pack'; import { existingInstall, assertVersion } from './kibana'; import { prepareExternalProjectDependencies } from '@kbn/pm'; @@ -46,7 +46,7 @@ export default async function install(settings, logger) { await extract(settings, logger); - rimrafSync(settings.tempArchiveFile); + del.sync(settings.tempArchiveFile); existingInstall(settings, logger); diff --git a/src/cli_plugin/install/kibana.test.js b/src/cli_plugin/install/kibana.test.js index 64de96b34e807a..f5485846274cec 100644 --- a/src/cli_plugin/install/kibana.test.js +++ b/src/cli_plugin/install/kibana.test.js @@ -17,19 +17,19 @@ * under the License. */ -jest.mock('fs', () => ({ - statSync: jest.fn().mockImplementation(() => require('fs').statSync), - unlinkSync: jest.fn().mockImplementation(() => require('fs').unlinkSync), - mkdirSync: jest.fn().mockImplementation(() => require('fs').mkdirSync), -})); - import sinon from 'sinon'; import Logger from '../lib/logger'; import { join } from 'path'; -import rimraf from 'rimraf'; +import del from 'del'; import fs from 'fs'; import { existingInstall, assertVersion } from './kibana'; +jest.spyOn(fs, 'statSync'); + +beforeEach(() => { + jest.clearAllMocks(); +}); + describe('kibana cli', function () { describe('plugin installer', function () { @@ -53,7 +53,7 @@ describe('kibana cli', function () { describe('assertVersion', function () { beforeEach(function () { - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); fs.mkdirSync(testWorkingPath, { recursive: true }); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); @@ -62,7 +62,7 @@ describe('kibana cli', function () { afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); }); it('should succeed with exact match', function () { @@ -124,14 +124,14 @@ describe('kibana cli', function () { }); it('should throw an error if the plugin already exists.', function () { - fs.statSync = jest.fn().mockImplementationOnce(() => true); + fs.statSync.mockImplementationOnce(() => true); existingInstall(settings, logger); expect(logger.error.firstCall.args[0]).toMatch(/already exists/); expect(process.exit.called).toBe(true); }); it('should not throw an error if the plugin does not exist.', function () { - fs.statSync = jest.fn().mockImplementationOnce(() => { + fs.statSync.mockImplementationOnce(() => { throw { code: 'ENOENT' }; }); existingInstall(settings, logger); diff --git a/src/cli_plugin/install/pack.test.js b/src/cli_plugin/install/pack.test.js index 1bc2504397fb52..b599934b1d99bc 100644 --- a/src/cli_plugin/install/pack.test.js +++ b/src/cli_plugin/install/pack.test.js @@ -21,7 +21,7 @@ import Fs from 'fs'; import sinon from 'sinon'; import glob from 'glob-all'; -import rimraf from 'rimraf'; +import del from 'del'; import Logger from '../lib/logger'; import { extract, getPackData } from './pack'; import { _downloadSingle } from './download'; @@ -41,7 +41,7 @@ describe('kibana cli', function () { beforeEach(function () { //These tests are dependent on the file system, and I had some inconsistent - //behavior with rimraf.sync show up. Until these tests are re-written to not + //behavior with del.sync show up. Until these tests are re-written to not //depend on the file system, I make sure that each test uses a different //working directory. testNum += 1; @@ -65,7 +65,7 @@ describe('kibana cli', function () { afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(workingPathRoot); + del.sync(workingPathRoot); }); function copyReplyFile(filename) { diff --git a/src/cli_plugin/install/zip.test.js b/src/cli_plugin/install/zip.test.js index 340dec196eef5b..0bcc1c182ff0d7 100644 --- a/src/cli_plugin/install/zip.test.js +++ b/src/cli_plugin/install/zip.test.js @@ -17,7 +17,7 @@ * under the License. */ -import rimraf from 'rimraf'; +import del from 'del'; import path from 'path'; import os from 'os'; import glob from 'glob'; @@ -38,7 +38,7 @@ describe('kibana cli', function () { }); afterEach(() => { - rimraf.sync(tempPath); + del.sync(tempPath, { force: true }); }); describe('analyzeArchive', function () { diff --git a/src/cli_plugin/list/list.test.js b/src/cli_plugin/list/list.test.js index 425a6bee5394e1..5a40fcc793e08b 100644 --- a/src/cli_plugin/list/list.test.js +++ b/src/cli_plugin/list/list.test.js @@ -18,7 +18,7 @@ */ import sinon from 'sinon'; -import rimraf from 'rimraf'; +import del from 'del'; import Logger from '../lib/logger'; import list from './list'; import { join } from 'path'; @@ -46,14 +46,14 @@ describe('kibana cli', function () { logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - rimraf.sync(pluginDir); + del.sync(pluginDir); mkdirSync(pluginDir, { recursive: true }); }); afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(pluginDir); + del.sync(pluginDir); }); it('list all of the folders in the plugin folder', function () { diff --git a/src/cli_plugin/remove/remove.js b/src/cli_plugin/remove/remove.js index 7aaccd8ca05fe5..8432d0f44836ba 100644 --- a/src/cli_plugin/remove/remove.js +++ b/src/cli_plugin/remove/remove.js @@ -20,7 +20,7 @@ import { statSync } from 'fs'; import { errorIfXPackRemove } from '../lib/error_if_x_pack'; -import rimraf from 'rimraf'; +import del from 'del'; export default function remove(settings, logger) { try { @@ -37,7 +37,7 @@ export default function remove(settings, logger) { } logger.log(`Removing ${settings.plugin}...`); - rimraf.sync(settings.pluginPath); + del.sync(settings.pluginPath); logger.log('Plugin removal complete'); } catch (err) { logger.error(`Unable to remove plugin because of error: "${err.message}"`); diff --git a/src/cli_plugin/remove/remove.test.js b/src/cli_plugin/remove/remove.test.js index 5d936d02785210..c063a713f47aca 100644 --- a/src/cli_plugin/remove/remove.test.js +++ b/src/cli_plugin/remove/remove.test.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import glob from 'glob-all'; -import rimraf from 'rimraf'; +import del from 'del'; import Logger from '../lib/logger'; import remove from './remove'; import { join } from 'path'; @@ -40,7 +40,7 @@ describe('kibana cli', function () { logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - rimraf.sync(pluginDir); + del.sync(pluginDir); mkdirSync(pluginDir, { recursive: true }); }); @@ -48,7 +48,7 @@ describe('kibana cli', function () { processExitStub.restore(); logger.log.restore(); logger.error.restore(); - rimraf.sync(pluginDir); + del.sync(pluginDir); }); it('throw an error if the plugin is not installed.', function () { diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 36883440e0b5dc..c26a383719fa1a 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -174,10 +174,10 @@ In the new platform, there are three lifecycle functions today: `setup`, `start` The table below explains how each lifecycle event relates to the state of Kibana. | lifecycle event | server | browser | -|-----------------|-------------------------------------------|-----------------------------------------------------| +| --------------- | ----------------------------------------- | --------------------------------------------------- | | *setup* | bootstrapping and configuring routes | loading plugin bundles and configuring applications | | *start* | server is now serving traffic | browser is now showing UI to the user | -| *stop* | server has received a request to shutdown | user is navigating away from Kibana | +| *stop* | server has received a request to shutdown | user is navigating away from Kibana | There is no equivalent behavior to `start` or `stop` in legacy plugins, so this guide primarily focuses on migrating functionality into `setup`. @@ -1096,7 +1096,7 @@ import { npStart: { core } } from 'ui/new_platform'; ``` | Legacy Platform | New Platform | Notes | -|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | | `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | | `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | @@ -1111,6 +1111,7 @@ import { npStart: { core } } from 'ui/new_platform'; | `ui/notify` | [`core.notifications`](/docs/development/core/public/kibana-plugin-public.notificationsstart.md) and [`core.overlays`](/docs/development/core/public/kibana-plugin-public.overlaystart.md) | Toast messages are in `notifications`, banners are in `overlays`. May be combined later. | | `ui/routes` | -- | There is no global routing mechanism. Each app [configures its own routing](/rfcs/text/0004_application_service_mounting.md#complete-example). | | `ui/saved_objects` | [`core.savedObjects`](/docs/development/core/public/kibana-plugin-public.savedobjectsstart.md) | Client API is the same | +| `ui/doc_title` | [`core.chrome.docTitle`](/docs/development/core/public/kibana-plugin-public.chromedoctitle.md) | | _See also: [Public's CoreStart API Docs](/docs/development/core/public/kibana-plugin-public.corestart.md)_ @@ -1125,24 +1126,25 @@ import { setup, start } from '../core_plugins/embeddables/public/legacy'; import { setup, start } from '../core_plugins/visualizations/public/legacy'; ``` -| Legacy Platform | New Platform | Notes | -|--------------------------------------------------------|--------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| -| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | -| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | -| `import 'ui/query_bar'` | `import { QueryBar, QueryBarInput } from '../data/public'` | Directives are deprecated. | -| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | -| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../kibana_react/public'` | Directive is still available in `ui/kbn_top_nav`. | -| `core_plugins/interpreter` | `data.expressions` | still in progress | -| `ui/courier` | `data.search` | still in progress | -| `ui/embeddable` | `embeddables` | still in progress | -| `ui/filter_manager` | `data.filter` | -- | -| `ui/index_patterns` | `data.indexPatterns` | still in progress | -| `ui/registry/vis_types` | `visualizations.types` | -- | -| `ui/vis` | `visualizations.types` | -- | -| `ui/vis/vis_factory` | `visualizations.types` | -- | -| `ui/vis/vis_filters` | `visualizations.filters` | -- | -| `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | - +| Legacy Platform | New Platform | Notes | +| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | +| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | +| `import 'ui/query_bar'` | `import { QueryBar, QueryBarInput } from '../data/public'` | Directives are deprecated. | +| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | +| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. | +| `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | | +| `core_plugins/interpreter` | `data.expressions` | still in progress | +| `ui/courier` | `data.search` | still in progress | +| `ui/embeddable` | `embeddables` | still in progress | +| `ui/filter_manager` | `data.filter` | -- | +| `ui/index_patterns` | `data.indexPatterns` | still in progress | +| `ui/registry/feature_catalogue | `feature_catalogue.register` | Must add `feature_catalogue` as a dependency in your kibana.json. | +| `ui/registry/vis_types` | `visualizations.types` | -- | +| `ui/vis` | `visualizations.types` | -- | +| `ui/vis/vis_factory` | `visualizations.types` | -- | +| `ui/vis/vis_filters` | `visualizations.filters` | -- | +| `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | #### Server-side @@ -1150,9 +1152,9 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; In server code, `core` can be accessed from either `server.newPlatform` or `kbnServer.newPlatform`. There are not currently very many services available on the server-side: | Legacy Platform | New Platform | Notes | -|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | -| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | +| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client | | `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client | @@ -1166,7 +1168,7 @@ The legacy platform uses a set of "uiExports" to inject modules from one plugin This table shows where these uiExports have moved to in the New Platform. In most cases, if a uiExport you need is not yet available in the New Platform, you may leave in your legacy plugin for the time being and continue to migrate the rest of your app to the New Platform. | Legacy Platform | New Platform | Notes | -|------------------------------|---------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `aliases` | | | | `app` | [`core.application.register`](/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md) | | | `canvas` | | Should be an API on the canvas plugin. | @@ -1179,7 +1181,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `fieldFormatEditors` | | | | `fieldFormats` | | | | `hacks` | n/a | Just run the code in your plugin's `start` method. | -| `home` | | Should be an API on the home plugin. | +| `home` | [`plugins.feature_catalogue.register`](./src/plugins/feature_catalogue) | Must add `feature_catalogue` as a dependency in your kibana.json. | | `indexManagement` | | Should be an API on the indexManagement plugin. | | `injectDefaultVars` | n/a | Plugins will only be able to "whitelist" config values for the frontend. See [#41990](https://github.com/elastic/kibana/issues/41990) | | `inspectorViews` | | Should be an API on the data (?) plugin. | @@ -1197,7 +1199,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `styleSheetPaths` | | | | `taskDefinitions` | | Should be an API on the taskManager plugin. | | `uiCapabilities` | [`core.application.register`](/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md) | | -| `uiSettingDefaults` | | Most likely part of server-side UiSettingsService. | +| `uiSettingDefaults` | [`core.uiSettings.register`](/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md) | | | `validations` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | | `visEditorTypes` | | | | `visTypeEnhancers` | | | diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 3775989c5126b4..6f61ee9dc21ba5 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -43,6 +43,13 @@ const createStartContractMock = () => { get: jest.fn(), get$: jest.fn(), }, + docTitle: { + change: jest.fn(), + reset: jest.fn(), + __legacy: { + setBaseTitle: jest.fn(), + }, + }, navControls: { registerLeft: jest.fn(), registerRight: jest.fn(), diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 71279ad6fed03a..87389d2c10f033 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -33,10 +33,11 @@ import { HttpStart } from '../http'; import { ChromeNavLinks, NavLinksService } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { NavControlsService, ChromeNavControls } from './nav_controls'; +import { DocTitleService, ChromeDocTitle } from './doc_title'; import { LoadingIndicator, HeaderWrapper as Header } from './ui'; import { DocLinksStart } from '../doc_links'; -export { ChromeNavControls, ChromeRecentlyAccessed }; +export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed'; @@ -82,6 +83,7 @@ export class ChromeService { private readonly navControls = new NavControlsService(); private readonly navLinks = new NavLinksService(); private readonly recentlyAccessed = new RecentlyAccessedService(); + private readonly docTitle = new DocTitleService(); constructor(private readonly params: ConstructorParams) {} @@ -106,6 +108,7 @@ export class ChromeService { const navControls = this.navControls.start(); const navLinks = this.navLinks.start({ application, http }); const recentlyAccessed = await this.recentlyAccessed.start({ http }); + const docTitle = this.docTitle.start({ document: window.document }); if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) { notifications.toasts.addWarning( @@ -119,6 +122,7 @@ export class ChromeService { navControls, navLinks, recentlyAccessed, + docTitle, getHeaderComponent: () => ( @@ -259,6 +263,8 @@ export interface ChromeStart { navControls: ChromeNavControls; /** {@inheritdoc ChromeRecentlyAccessed} */ recentlyAccessed: ChromeRecentlyAccessed; + /** {@inheritdoc ChromeDocTitle} */ + docTitle: ChromeDocTitle; /** * Sets the current app's title diff --git a/src/core/public/chrome/doc_title/doc_title_service.test.ts b/src/core/public/chrome/doc_title/doc_title_service.test.ts new file mode 100644 index 00000000000000..763e8c9ebd74a5 --- /dev/null +++ b/src/core/public/chrome/doc_title/doc_title_service.test.ts @@ -0,0 +1,80 @@ +/* + * 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 { DocTitleService } from './doc_title_service'; + +describe('DocTitleService', () => { + const defaultTitle = 'KibanaTest'; + const document = { title: '' }; + + const getStart = (title: string = defaultTitle) => { + document.title = title; + return new DocTitleService().start({ document }); + }; + + beforeEach(() => { + document.title = defaultTitle; + }); + + describe('#change()', () => { + it('changes the title of the document', async () => { + getStart().change('TitleA'); + expect(document.title).toEqual('TitleA - KibanaTest'); + }); + + it('appends the baseTitle to the title', async () => { + const start = getStart('BaseTitle'); + start.change('TitleA'); + expect(document.title).toEqual('TitleA - BaseTitle'); + start.change('TitleB'); + expect(document.title).toEqual('TitleB - BaseTitle'); + }); + + it('accepts string arrays as input', async () => { + const start = getStart(); + start.change(['partA', 'partB']); + expect(document.title).toEqual(`partA - partB - ${defaultTitle}`); + start.change(['partA', 'partB', 'partC']); + expect(document.title).toEqual(`partA - partB - partC - ${defaultTitle}`); + }); + }); + + describe('#reset()', () => { + it('resets the title to the initial value', async () => { + const start = getStart('InitialTitle'); + start.change('TitleA'); + expect(document.title).toEqual('TitleA - InitialTitle'); + start.reset(); + expect(document.title).toEqual('InitialTitle'); + }); + }); + + describe('#__legacy.setBaseTitle()', () => { + it('allows to change the baseTitle after startup', async () => { + const start = getStart('InitialTitle'); + start.change('WithInitial'); + expect(document.title).toEqual('WithInitial - InitialTitle'); + start.__legacy.setBaseTitle('NewBaseTitle'); + start.change('WithNew'); + expect(document.title).toEqual('WithNew - NewBaseTitle'); + start.reset(); + expect(document.title).toEqual('NewBaseTitle'); + }); + }); +}); diff --git a/src/core/public/chrome/doc_title/doc_title_service.ts b/src/core/public/chrome/doc_title/doc_title_service.ts new file mode 100644 index 00000000000000..9453abe54de660 --- /dev/null +++ b/src/core/public/chrome/doc_title/doc_title_service.ts @@ -0,0 +1,105 @@ +/* + * 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, flattenDeep, isString } from 'lodash'; + +interface StartDeps { + document: { title: string }; +} + +/** + * APIs for accessing and updating the document title. + * + * @example + * How to change the title of the document + * ```ts + * chrome.docTitle.change('My application') + * ``` + * + * @example + * How to reset the title of the document to it's initial value + * ```ts + * chrome.docTitle.reset() + * ``` + * + * @public + * */ +export interface ChromeDocTitle { + /** + * Changes the current document title. + * + * @example + * How to change the title of the document + * ```ts + * chrome.docTitle.change('My application title') + * chrome.docTitle.change(['My application', 'My section']) + * ``` + * + * @param newTitle The new title to set, either a string or string array + */ + change(newTitle: string | string[]): void; + /** + * Resets the document title to it's initial value. + * (meaning the one present in the title meta at application load.) + */ + reset(): void; + + /** @internal */ + __legacy: { + setBaseTitle(baseTitle: string): void; + }; +} + +const defaultTitle: string[] = []; +const titleSeparator = ' - '; + +/** @internal */ +export class DocTitleService { + private document = { title: '' }; + private baseTitle = ''; + + public start({ document }: StartDeps): ChromeDocTitle { + this.document = document; + this.baseTitle = document.title; + + return { + change: (title: string | string[]) => { + this.applyTitle(title); + }, + reset: () => { + this.applyTitle(defaultTitle); + }, + __legacy: { + setBaseTitle: baseTitle => { + this.baseTitle = baseTitle; + }, + }, + }; + } + + private applyTitle(title: string | string[]) { + this.document.title = this.render(title); + } + + private render(title: string | string[]) { + const parts = [...(isString(title) ? [title] : title), this.baseTitle]; + // ensuring compat with legacy that might be passing nested arrays + return compact(flattenDeep(parts)).join(titleSeparator); + } +} diff --git a/src/core/public/chrome/doc_title/index.ts b/src/core/public/chrome/doc_title/index.ts new file mode 100644 index 00000000000000..b070d01953f7ab --- /dev/null +++ b/src/core/public/chrome/doc_title/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 './doc_title_service'; diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index 6e03f9e023983f..b220a81f775f8d 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -29,3 +29,4 @@ export { export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields } from './nav_links'; export { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem } from './recently_accessed'; export { ChromeNavControl, ChromeNavControls } from './nav_controls'; +export { ChromeDocTitle } from './doc_title'; diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index bc723de06f2159..3ddbe420ba2842 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -171,7 +171,7 @@ interface Props { navLinks$: Rx.Observable; recentlyAccessed$: Rx.Observable; forceAppSwitcherNavigation$: Rx.Observable; - helpExtension$: Rx.Observable; + helpExtension$: Rx.Observable; legacyMode: boolean; navControlsLeft$: Rx.Observable; navControlsRight$: Rx.Observable; diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx index e2146f8d65bbae..688d76c9d75d9c 100644 --- a/src/core/public/chrome/ui/header/header_help_menu.tsx +++ b/src/core/public/chrome/ui/header/header_help_menu.tsx @@ -43,7 +43,7 @@ import { HeaderExtension } from './header_extension'; import { ChromeHelpExtension } from '../../chrome_service'; interface Props { - helpExtension$: Rx.Observable; + helpExtension$: Rx.Observable; intl: InjectedIntl; kibanaVersion: string; useDefaultContent?: boolean; diff --git a/src/core/public/core_system.test.mocks.ts b/src/core/public/core_system.test.mocks.ts index d2494badfacdb5..75ab6cdb628f79 100644 --- a/src/core/public/core_system.test.mocks.ts +++ b/src/core/public/core_system.test.mocks.ts @@ -31,6 +31,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { renderingServiceMock } from './rendering/rendering_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { integrationsServiceMock } from './integrations/integrations_service.mock'; export const MockLegacyPlatformService = legacyPlatformServiceMock.create(); export const LegacyPlatformServiceConstructor = jest @@ -127,3 +128,11 @@ export const ContextServiceConstructor = jest.fn().mockImplementation(() => Mock jest.doMock('./context', () => ({ ContextService: ContextServiceConstructor, })); + +export const MockIntegrationsService = integrationsServiceMock.create(); +export const IntegrationsServiceConstructor = jest + .fn() + .mockImplementation(() => MockIntegrationsService); +jest.doMock('./integrations', () => ({ + IntegrationsService: IntegrationsServiceConstructor, +})); diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index ec6f020a3490b0..d78504a899a345 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -42,6 +42,8 @@ import { MockRenderingService, RenderingServiceConstructor, MockContextService, + IntegrationsServiceConstructor, + MockIntegrationsService, } from './core_system.test.mocks'; import { CoreSystem } from './core_system'; @@ -86,6 +88,7 @@ describe('constructor', () => { expect(ChromeServiceConstructor).toHaveBeenCalledTimes(1); expect(OverlayServiceConstructor).toHaveBeenCalledTimes(1); expect(RenderingServiceConstructor).toHaveBeenCalledTimes(1); + expect(IntegrationsServiceConstructor).toHaveBeenCalledTimes(1); }); it('passes injectedMetadata param to InjectedMetadataService', () => { @@ -213,6 +216,11 @@ describe('#setup()', () => { await setupCore(); expect(MockPluginsService.setup).toHaveBeenCalledTimes(1); }); + + it('calls integrations#setup()', async () => { + await setupCore(); + expect(MockIntegrationsService.setup).toHaveBeenCalledTimes(1); + }); }); describe('#start()', () => { @@ -296,6 +304,11 @@ describe('#start()', () => { targetDomElement: expect.any(HTMLElement), }); }); + + it('calls start#setup()', async () => { + await startCore(); + expect(MockIntegrationsService.start).toHaveBeenCalledTimes(1); + }); }); describe('#stop()', () => { @@ -346,6 +359,14 @@ describe('#stop()', () => { expect(MockI18nService.stop).toHaveBeenCalled(); }); + it('calls integrations.stop()', () => { + const coreSystem = createCoreSystem(); + + expect(MockIntegrationsService.stop).not.toHaveBeenCalled(); + coreSystem.stop(); + expect(MockIntegrationsService.stop).toHaveBeenCalled(); + }); + it('clears the rootDomElement', async () => { const rootDomElement = document.createElement('div'); const coreSystem = createCoreSystem({ diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 71e9c39df7aae2..2404459ad13834 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -41,8 +41,9 @@ import { ApplicationService } from './application'; import { mapToObject, pick } from '../utils/'; import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; -import { SavedObjectsService } from './saved_objects/saved_objects_service'; +import { SavedObjectsService } from './saved_objects'; import { ContextService } from './context'; +import { IntegrationsService } from './integrations'; import { InternalApplicationSetup, InternalApplicationStart } from './application/types'; interface Params { @@ -98,6 +99,7 @@ export class CoreSystem { private readonly docLinks: DocLinksService; private readonly rendering: RenderingService; private readonly context: ContextService; + private readonly integrations: IntegrationsService; private readonly rootDomElement: HTMLElement; private readonly coreContext: CoreContext; @@ -134,6 +136,7 @@ export class CoreSystem { this.docLinks = new DocLinksService(); this.rendering = new RenderingService(); this.application = new ApplicationService(); + this.integrations = new IntegrationsService(); this.coreContext = { coreId: Symbol('core'), env: injectedMetadata.env }; @@ -155,6 +158,7 @@ export class CoreSystem { injectedMetadata, i18n: this.i18n.getContext(), }); + await this.integrations.setup(); const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const uiSettings = this.uiSettings.setup({ http, injectedMetadata }); const notifications = this.notifications.setup({ uiSettings }); @@ -211,6 +215,7 @@ export class CoreSystem { const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); const application = await this.application.start({ http, injectedMetadata }); + await this.integrations.start({ uiSettings }); const coreUiTargetDomElement = document.createElement('div'); coreUiTargetDomElement.id = 'kibana-body'; @@ -297,6 +302,7 @@ export class CoreSystem { this.plugins.stop(); this.notifications.stop(); this.http.stop(); + this.integrations.stop(); this.uiSettings.stop(); this.chrome.stop(); this.i18n.stop(); diff --git a/src/core/public/http/anonymous_paths.test.ts b/src/core/public/http/anonymous_paths.test.ts new file mode 100644 index 00000000000000..bf9212f625f1e4 --- /dev/null +++ b/src/core/public/http/anonymous_paths.test.ts @@ -0,0 +1,107 @@ +/* + * 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 { AnonymousPaths } from './anonymous_paths'; +import { BasePath } from './base_path_service'; + +describe('#register', () => { + it(`allows paths that don't start with /`, () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('bar'); + }); + + it(`allows paths that end with '/'`, () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar/'); + }); +}); + +describe('#isAnonymous', () => { + it('returns true for registered paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered with a trailing slash, but call "isAnonymous" with no trailing slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar/'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered without a trailing slash, but call "isAnonymous" with a trailing slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar/')).toBe(true); + }); + + it('returns true for paths registered without a starting slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('when there is no basePath and calling "isAnonymous" without a starting slash, returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('bar')).toBe(true); + }); + + it('when there is no basePath and calling "isAnonymous" with a starting slash, returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/bar')).toBe(true); + }); + + it('returns true for paths whose capitalization is different', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/BAR'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns false for other paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/foo')).toBe(false); + }); + + it('returns false for sub-paths of registered paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar/baz')).toBe(false); + }); +}); diff --git a/src/core/public/http/anonymous_paths.ts b/src/core/public/http/anonymous_paths.ts new file mode 100644 index 00000000000000..300c4d64df353c --- /dev/null +++ b/src/core/public/http/anonymous_paths.ts @@ -0,0 +1,53 @@ +/* + * 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 { IAnonymousPaths, IBasePath } from 'src/core/public'; + +export class AnonymousPaths implements IAnonymousPaths { + private readonly paths = new Set(); + + constructor(private basePath: IBasePath) {} + + public isAnonymous(path: string): boolean { + const pathWithoutBasePath = this.basePath.remove(path); + return this.paths.has(this.normalizePath(pathWithoutBasePath)); + } + + public register(path: string) { + this.paths.add(this.normalizePath(path)); + } + + private normalizePath(path: string) { + // always lower-case it + let normalized = path.toLowerCase(); + + // remove the slash from the end + if (normalized.endsWith('/')) { + normalized = normalized.slice(0, normalized.length - 1); + } + + // put a slash at the start + if (!normalized.startsWith('/')) { + normalized = `/${normalized}`; + } + + // it's normalized!!! + return normalized; + } +} diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index a94543414acfaa..52f188c7b20a0a 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -20,9 +20,11 @@ import { HttpService } from './http_service'; import { HttpSetup } from './types'; import { BehaviorSubject } from 'rxjs'; +import { BasePath } from './base_path_service'; +import { AnonymousPaths } from './anonymous_paths'; type ServiceSetupMockType = jest.Mocked & { - basePath: jest.Mocked; + basePath: BasePath; }; const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ @@ -34,11 +36,8 @@ const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ patch: jest.fn(), delete: jest.fn(), options: jest.fn(), - basePath: { - get: jest.fn(() => basePath), - prepend: jest.fn(path => `${basePath}${path}`), - remove: jest.fn(), - }, + basePath: new BasePath(basePath), + anonymousPaths: new AnonymousPaths(new BasePath(basePath)), addLoadingCount: jest.fn(), getLoadingCount$: jest.fn().mockReturnValue(new BehaviorSubject(0)), stop: jest.fn(), diff --git a/src/core/public/http/http_setup.ts b/src/core/public/http/http_setup.ts index a10358926de1fd..602382e3a5a60e 100644 --- a/src/core/public/http/http_setup.ts +++ b/src/core/public/http/http_setup.ts @@ -36,6 +36,7 @@ import { HttpInterceptController } from './http_intercept_controller'; import { HttpFetchError } from './http_fetch_error'; import { HttpInterceptHaltError } from './http_intercept_halt_error'; import { BasePath } from './base_path_service'; +import { AnonymousPaths } from './anonymous_paths'; const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/; const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/; @@ -57,6 +58,7 @@ export const setup = ( const interceptors = new Set(); const kibanaVersion = injectedMetadata.getKibanaVersion(); const basePath = new BasePath(injectedMetadata.getBasePath()); + const anonymousPaths = new AnonymousPaths(basePath); function intercept(interceptor: HttpInterceptor) { interceptors.add(interceptor); @@ -318,6 +320,7 @@ export const setup = ( return { stop, basePath, + anonymousPaths, intercept, removeAllInterceptors, fetch, diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 96500d566b3e53..870d4af8f9e861 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -29,6 +29,11 @@ export interface HttpServiceBase { */ basePath: IBasePath; + /** + * APIs for denoting certain paths for not requiring authentication + */ + anonymousPaths: IAnonymousPaths; + /** * Adds a new {@link HttpInterceptor} to the global HTTP client. * @param interceptor a {@link HttpInterceptor} @@ -92,6 +97,21 @@ export interface IBasePath { remove: (url: string) => string; } +/** + * APIs for denoting paths as not requiring authentication + */ +export interface IAnonymousPaths { + /** + * Determines whether the provided path doesn't require authentication. `path` should include the current basePath. + */ + isAnonymous(path: string): boolean; + + /** + * Register `path` as not requiring authentication. `path` should not include the current basePath. + */ + register(path: string): void; +} + /** * See {@link HttpServiceBase} * @public diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 3d8714a0011586..7391cf7f9454cc 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -45,6 +45,7 @@ import { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields, + ChromeDocTitle, ChromeStart, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, @@ -110,6 +111,7 @@ export { HttpHandler, HttpBody, IBasePath, + IAnonymousPaths, IHttpInterceptController, IHttpFetchError, InterceptedHttpResponse, @@ -249,6 +251,7 @@ export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields, + ChromeDocTitle, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, ChromeStart, diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 478285a70771ef..a5342aaa48b723 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -19,8 +19,12 @@ import { get } from 'lodash'; import { DiscoveredPlugin, PluginName } from '../../server'; -import { EnvironmentMode, PackageInfo } from '../../server/types'; -import { UiSettingsState } from '../ui_settings'; +import { + EnvironmentMode, + PackageInfo, + UiSettingsParams, + UserProvidedValues, +} from '../../server/types'; import { deepFreeze } from '../../utils/'; import { Capabilities } from '..'; @@ -69,8 +73,8 @@ export interface InjectedMetadataParams { serverName: string; devMode: boolean; uiSettings: { - defaults: UiSettingsState; - user?: UiSettingsState; + defaults: Record; + user?: Record; }; }; }; @@ -179,8 +183,8 @@ export interface InjectedMetadataSetup { serverName: string; devMode: boolean; uiSettings: { - defaults: UiSettingsState; - user?: UiSettingsState | undefined; + defaults: Record; + user?: Record | undefined; }; }; getInjectedVar: (name: string, defaultValue?: any) => unknown; diff --git a/src/core/public/integrations/index.ts b/src/core/public/integrations/index.ts new file mode 100644 index 00000000000000..efa94560933599 --- /dev/null +++ b/src/core/public/integrations/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 { IntegrationsService } from './integrations_service'; diff --git a/src/core/public/integrations/integrations_service.mock.ts b/src/core/public/integrations/integrations_service.mock.ts new file mode 100644 index 00000000000000..4f6ca0fb684597 --- /dev/null +++ b/src/core/public/integrations/integrations_service.mock.ts @@ -0,0 +1,31 @@ +/* + * 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 { IntegrationsService } from './integrations_service'; + +type IntegrationsServiceContract = PublicMethodsOf; +const createMock = (): jest.Mocked => ({ + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), +}); + +export const integrationsServiceMock = { + create: createMock, +}; diff --git a/src/core/public/integrations/integrations_service.test.mocks.ts b/src/core/public/integrations/integrations_service.test.mocks.ts new file mode 100644 index 00000000000000..82535724565a71 --- /dev/null +++ b/src/core/public/integrations/integrations_service.test.mocks.ts @@ -0,0 +1,38 @@ +/* + * 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 { CoreService } from '../../types'; + +const createCoreServiceMock = (): jest.Mocked => { + return { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; +}; + +export const styleServiceMock = createCoreServiceMock(); +jest.doMock('./styles', () => ({ + StylesService: jest.fn(() => styleServiceMock), +})); + +export const momentServiceMock = createCoreServiceMock(); +jest.doMock('./moment', () => ({ + MomentService: jest.fn(() => momentServiceMock), +})); diff --git a/src/core/public/integrations/integrations_service.test.ts b/src/core/public/integrations/integrations_service.test.ts new file mode 100644 index 00000000000000..03784c92e12abc --- /dev/null +++ b/src/core/public/integrations/integrations_service.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { styleServiceMock, momentServiceMock } from './integrations_service.test.mocks'; + +import { IntegrationsService } from './integrations_service'; +import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; + +describe('IntegrationsService', () => { + test('it wires up styles and moment', async () => { + const uiSettings = uiSettingsServiceMock.createStartContract(); + const service = new IntegrationsService(); + + await service.setup(); + expect(styleServiceMock.setup).toHaveBeenCalledWith(); + expect(momentServiceMock.setup).toHaveBeenCalledWith(); + + await service.start({ uiSettings }); + expect(styleServiceMock.start).toHaveBeenCalledWith({ uiSettings }); + expect(momentServiceMock.start).toHaveBeenCalledWith({ uiSettings }); + + await service.stop(); + expect(styleServiceMock.stop).toHaveBeenCalledWith(); + expect(momentServiceMock.stop).toHaveBeenCalledWith(); + }); +}); diff --git a/src/core/public/integrations/integrations_service.ts b/src/core/public/integrations/integrations_service.ts new file mode 100644 index 00000000000000..5d5c31c2df18ce --- /dev/null +++ b/src/core/public/integrations/integrations_service.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 { UiSettingsClientContract } from '../ui_settings'; +import { CoreService } from '../../types'; + +import { MomentService } from './moment'; +import { StylesService } from './styles'; + +interface Deps { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export class IntegrationsService implements CoreService { + private readonly styles = new StylesService(); + private readonly moment = new MomentService(); + + public async setup() { + await this.styles.setup(); + await this.moment.setup(); + } + + public async start({ uiSettings }: Deps) { + await this.styles.start({ uiSettings }); + await this.moment.start({ uiSettings }); + } + + public async stop() { + await this.styles.stop(); + await this.moment.stop(); + } +} diff --git a/src/core/public/integrations/moment/index.ts b/src/core/public/integrations/moment/index.ts new file mode 100644 index 00000000000000..9c7c140bff64dd --- /dev/null +++ b/src/core/public/integrations/moment/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 { MomentService } from './moment_service'; diff --git a/src/core/public/integrations/moment/moment_service.test.mocks.ts b/src/core/public/integrations/moment/moment_service.test.mocks.ts new file mode 100644 index 00000000000000..2fa013a6bf1321 --- /dev/null +++ b/src/core/public/integrations/moment/moment_service.test.mocks.ts @@ -0,0 +1,28 @@ +/* + * 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 const momentMock = { + locale: jest.fn(() => 'default-locale'), + tz: { + setDefault: jest.fn(), + }, + weekdays: jest.fn(() => ['dow1', 'dow2', 'dow3']), + updateLocale: jest.fn(), +}; +jest.doMock('moment-timezone', () => momentMock); diff --git a/src/core/public/integrations/moment/moment_service.test.ts b/src/core/public/integrations/moment/moment_service.test.ts new file mode 100644 index 00000000000000..c9b4479f2f1636 --- /dev/null +++ b/src/core/public/integrations/moment/moment_service.test.ts @@ -0,0 +1,71 @@ +/* + * 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 { momentMock } from './moment_service.test.mocks'; +import { MomentService } from './moment_service'; +import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; +import { BehaviorSubject } from 'rxjs'; + +describe('MomentService', () => { + let service: MomentService; + beforeEach(() => { + momentMock.tz.setDefault.mockClear(); + momentMock.weekdays.mockClear(); + momentMock.updateLocale.mockClear(); + service = new MomentService(); + }); + afterEach(() => service.stop()); + + const flushPromises = () => new Promise(resolve => setTimeout(resolve, 100)); + + test('sets initial moment config', async () => { + const tz$ = new BehaviorSubject('tz1'); + const dow$ = new BehaviorSubject('dow1'); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(tz$).mockReturnValueOnce(dow$); + + service.start({ uiSettings }); + await flushPromises(); + expect(momentMock.tz.setDefault).toHaveBeenCalledWith('tz1'); + expect(momentMock.updateLocale).toHaveBeenCalledWith('default-locale', { week: { dow: 0 } }); + }); + + test('updates moment config', async () => { + const tz$ = new BehaviorSubject('tz1'); + const dow$ = new BehaviorSubject('dow1'); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(tz$).mockReturnValueOnce(dow$); + + service.start({ uiSettings }); + tz$.next('tz2'); + tz$.next('tz3'); + dow$.next('dow3'); + dow$.next('dow2'); + + await flushPromises(); + expect(momentMock.tz.setDefault.mock.calls).toEqual([['tz1'], ['tz2'], ['tz3']]); + expect(momentMock.updateLocale.mock.calls).toEqual([ + ['default-locale', { week: { dow: 0 } }], + ['default-locale', { week: { dow: 2 } }], + ['default-locale', { week: { dow: 1 } }], + ]); + }); +}); diff --git a/src/core/public/integrations/moment/moment_service.ts b/src/core/public/integrations/moment/moment_service.ts new file mode 100644 index 00000000000000..2714750d9a65ed --- /dev/null +++ b/src/core/public/integrations/moment/moment_service.ts @@ -0,0 +1,56 @@ +/* + * 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-timezone'; +import { merge, Subscription } from 'rxjs'; + +import { tap } from 'rxjs/operators'; +import { UiSettingsClientContract } from '../../ui_settings'; +import { CoreService } from '../../../types'; + +interface StartDeps { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export class MomentService implements CoreService { + private uiSettingsSubscription?: Subscription; + + public async setup() {} + + public async start({ uiSettings }: StartDeps) { + const setDefaultTimezone = (tz: string) => moment.tz.setDefault(tz); + const setStartDayOfWeek = (day: string) => { + const dow = moment.weekdays().indexOf(day); + moment.updateLocale(moment.locale(), { week: { dow } } as any); + }; + + this.uiSettingsSubscription = merge( + uiSettings.get$('dateFormat:tz').pipe(tap(setDefaultTimezone)), + uiSettings.get$('dateFormat:dow').pipe(tap(setStartDayOfWeek)) + ).subscribe(); + } + + public async stop() { + if (this.uiSettingsSubscription) { + this.uiSettingsSubscription.unsubscribe(); + this.uiSettingsSubscription = undefined; + } + } +} diff --git a/src/legacy/ui/public/styles/disable_animations/disable_animations.css b/src/core/public/integrations/styles/disable_animations.css similarity index 100% rename from src/legacy/ui/public/styles/disable_animations/disable_animations.css rename to src/core/public/integrations/styles/disable_animations.css diff --git a/src/core/public/integrations/styles/index.ts b/src/core/public/integrations/styles/index.ts new file mode 100644 index 00000000000000..6021eea8ee10ad --- /dev/null +++ b/src/core/public/integrations/styles/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 { StylesService } from './styles_service'; diff --git a/src/core/public/integrations/styles/styles_service.test.ts b/src/core/public/integrations/styles/styles_service.test.ts new file mode 100644 index 00000000000000..e413e9cc2f4d70 --- /dev/null +++ b/src/core/public/integrations/styles/styles_service.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; + +jest.mock('!!raw-loader!./disable_animations.css', () => 'MOCK DISABLE ANIMATIONS CSS'); + +import { StylesService } from './styles_service'; +import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; + +describe('StylesService', () => { + const flushPromises = () => new Promise(resolve => setTimeout(resolve, 100)); + const getDisableAnimationsTag = () => document.querySelector('style#disableAnimationsCss')!; + + afterEach(() => getDisableAnimationsTag().remove()); + + test('sets initial disable animations style', async () => { + const disableAnimations$ = new BehaviorSubject(false); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(disableAnimations$); + + new StylesService().start({ uiSettings }); + await flushPromises(); + + const styleTag = getDisableAnimationsTag(); + expect(styleTag).toBeDefined(); + expect(styleTag.textContent).toEqual(''); + }); + + test('updates disable animations style', async () => { + const disableAnimations$ = new BehaviorSubject(false); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(disableAnimations$); + + new StylesService().start({ uiSettings }); + + disableAnimations$.next(true); + await flushPromises(); + expect(getDisableAnimationsTag().textContent).toEqual('MOCK DISABLE ANIMATIONS CSS'); + + disableAnimations$.next(false); + await flushPromises(); + expect(getDisableAnimationsTag().textContent).toEqual(''); + }); +}); diff --git a/src/core/public/integrations/styles/styles_service.ts b/src/core/public/integrations/styles/styles_service.ts new file mode 100644 index 00000000000000..ba8b812fe99889 --- /dev/null +++ b/src/core/public/integrations/styles/styles_service.ts @@ -0,0 +1,57 @@ +/* + * 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 { Subscription } from 'rxjs'; + +import { UiSettingsClientContract } from '../../ui_settings'; +import { CoreService } from '../../../types'; +// @ts-ignore +import disableAnimationsCss from '!!raw-loader!./disable_animations.css'; + +interface StartDeps { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export class StylesService implements CoreService { + private uiSettingsSubscription?: Subscription; + + public async setup() {} + + public async start({ uiSettings }: StartDeps) { + const disableAnimationsStyleTag = document.createElement('style'); + disableAnimationsStyleTag.setAttribute('id', 'disableAnimationsCss'); + document.head.appendChild(disableAnimationsStyleTag); + + const setDisableAnimations = (disableAnimations: boolean) => { + disableAnimationsStyleTag.textContent = disableAnimations ? disableAnimationsCss : ''; + }; + + this.uiSettingsSubscription = uiSettings + .get$('accessibility:disableAnimations') + .subscribe(setDisableAnimations); + } + + public async stop() { + if (this.uiSettingsSubscription) { + this.uiSettingsSubscription.unsubscribe(); + this.uiSettingsSubscription = undefined; + } + } +} diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 8345980b6869d6..b9cd2577c22172 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -18,7 +18,7 @@ */ import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; -import { CoreContext, CoreSetup, CoreStart, PluginInitializerContext } from '.'; +import { CoreContext, CoreSetup, CoreStart, PluginInitializerContext, NotificationsSetup } from '.'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -41,12 +41,12 @@ export { notificationServiceMock } from './notifications/notifications_service.m export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; -function createCoreSetupMock() { - const mock: MockedKeys = { +function createCoreSetupMock({ basePath = '' } = {}) { + const mock: MockedKeys & { notifications: MockedKeys } = { application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), + http: httpServiceMock.createSetupContract({ basePath }), notifications: notificationServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), injectedMetadata: { @@ -57,12 +57,12 @@ function createCoreSetupMock() { return mock; } -function createCoreStartMock() { - const mock: MockedKeys = { +function createCoreStartMock({ basePath = '' } = {}) { + const mock: MockedKeys & { notifications: MockedKeys } = { application: applicationServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), - http: httpServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath }), i18n: i18nServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), overlays: overlayServiceMock.createStartContract(), diff --git a/src/core/public/notifications/notifications_service.mock.ts b/src/core/public/notifications/notifications_service.mock.ts index 8f4a1d5bcd4004..464f47e20aa3b0 100644 --- a/src/core/public/notifications/notifications_service.mock.ts +++ b/src/core/public/notifications/notifications_service.mock.ts @@ -23,10 +23,8 @@ import { } from './notifications_service'; import { toastsServiceMock } from './toasts/toasts_service.mock'; -type DeeplyMocked = { [P in keyof T]: jest.Mocked }; - const createSetupContractMock = () => { - const setupContract: DeeplyMocked = { + const setupContract: MockedKeys = { // we have to suppress type errors until decide how to mock es6 class toasts: toastsServiceMock.createSetupContract(), }; @@ -34,7 +32,7 @@ const createSetupContractMock = () => { }; const createStartContractMock = () => { - const startContract: DeeplyMocked = { + const startContract: MockedKeys = { // we have to suppress type errors until decide how to mock es6 class toasts: toastsServiceMock.createStartContract(), }; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index ec8a22fe5953cf..a596ea394abdaf 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -11,6 +11,8 @@ import React from 'react'; import * as Rx from 'rxjs'; import { ShallowPromise } from '@kbn/utility-types'; import { EuiGlobalToastListToast as Toast } from '@elastic/eui'; +import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types'; +import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @public export interface App extends AppBase { @@ -105,6 +107,16 @@ export interface ChromeBrand { // @public (undocumented) export type ChromeBreadcrumb = Breadcrumb; +// @public +export interface ChromeDocTitle { + // @internal (undocumented) + __legacy: { + setBaseTitle(baseTitle: string): void; + }; + change(newTitle: string | string[]): void; + reset(): void; +} + // @public (undocumented) export type ChromeHelpExtension = (element: HTMLDivElement) => () => void; @@ -186,6 +198,7 @@ export interface ChromeRecentlyAccessedHistoryItem { // @public export interface ChromeStart { addApplicationClass(className: string): void; + docTitle: ChromeDocTitle; getApplicationClasses$(): Observable; getBadge$(): Observable; getBrand$(): Observable; @@ -488,6 +501,7 @@ export interface HttpResponse extends InterceptedHttpResponse { // @public (undocumented) export interface HttpServiceBase { addLoadingCount(countSource$: Observable): void; + anonymousPaths: IAnonymousPaths; basePath: IBasePath; delete: HttpHandler; fetch: HttpHandler; @@ -517,6 +531,14 @@ export interface I18nStart { }) => JSX.Element; } +// Warning: (ae-missing-release-tag) "IAnonymousPaths" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface IAnonymousPaths { + isAnonymous(path: string): boolean; + register(path: string): void; +} + // @public export interface IBasePath { get: () => string; @@ -937,7 +959,7 @@ export class UiSettingsClient { constructor(params: UiSettingsClientParams); get$(key: string, defaultOverride?: any): Rx.Observable; get(key: string, defaultOverride?: any): any; - getAll(): UiSettingsState; + getAll(): Record>; getSaved$(): Rx.Observable<{ key: string; newValue: any; @@ -959,16 +981,13 @@ export class UiSettingsClient { stop(): void; } -// @public (undocumented) +// @public export type UiSettingsClientContract = PublicMethodsOf; // @public (undocumented) export interface UiSettingsState { - // Warning: (ae-forgotten-export) The symbol "InjectedUiSettingsDefault" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "InjectedUiSettingsUser" needs to be exported by the entry point index.d.ts - // // (undocumented) - [key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser; + [key: string]: UiSettingsParams_2 & UserProvidedValues_2; } diff --git a/src/core/public/rendering/rendering_service.test.tsx b/src/core/public/rendering/rendering_service.test.tsx index 317ab5cc8b855b..ed835574a32f9c 100644 --- a/src/core/public/rendering/rendering_service.test.tsx +++ b/src/core/public/rendering/rendering_service.test.tsx @@ -34,7 +34,7 @@ describe('RenderingService#start', () => { const chrome = chromeServiceMock.createStartContract(); chrome.getHeaderComponent.mockReturnValue(
Hello chrome!
); const overlays = overlayServiceMock.createStartContract(); - overlays.banners.getComponent.mockReturnValue(
I'm a banner!
); + overlays.banners.getComponent.mockReturnValue(
I'm a banner!
); const injectedMetadata = injectedMetadataServiceMock.createStartContract(); injectedMetadata.getLegacyMode.mockReturnValue(legacyMode); diff --git a/src/core/public/saved_objects/index.ts b/src/core/public/saved_objects/index.ts index 74d33c506db48d..453c3e42a1687b 100644 --- a/src/core/public/saved_objects/index.ts +++ b/src/core/public/saved_objects/index.ts @@ -30,7 +30,7 @@ export { SavedObjectsBulkUpdateOptions, } from './saved_objects_client'; export { SimpleSavedObject } from './simple_saved_object'; -export { SavedObjectsStart } from './saved_objects_service'; +export { SavedObjectsStart, SavedObjectsService } from './saved_objects_service'; export { SavedObject, SavedObjectAttribute, diff --git a/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap index f607c924a9e683..84f9a5ab7c5cd3 100644 --- a/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap +++ b/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap @@ -20,10 +20,20 @@ exports[`#setup constructs UiSettingsClient and UiSettingsApi: UiSettingsApi arg }, ], }, - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], diff --git a/src/core/public/ui_settings/types.ts b/src/core/public/ui_settings/types.ts index d1cf25c3e9e6f5..24e87eb04f0264 100644 --- a/src/core/public/ui_settings/types.ts +++ b/src/core/public/ui_settings/types.ts @@ -17,28 +17,9 @@ * under the License. */ -// properties that come from legacyInjectedMetadata.uiSettings.defaults -interface InjectedUiSettingsDefault { - name?: string; - value?: any; - description?: string; - category?: string[]; - type?: string; - readOnly?: boolean; - options?: string[] | { [key: string]: any }; - /** - * Whether a change in that setting will only take affect after a page reload. - */ - requiresPageReload?: boolean; -} - -// properties that come from legacyInjectedMetadata.uiSettings.user -interface InjectedUiSettingsUser { - userValue?: any; - isOverridden?: boolean; -} +import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types'; /** @public */ export interface UiSettingsState { - [key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser; + [key: string]: UiSettingsParams & UserProvidedValues; } diff --git a/src/core/public/ui_settings/ui_settings_client.ts b/src/core/public/ui_settings/ui_settings_client.ts index 3083851dd453d5..c3190847130d5e 100644 --- a/src/core/public/ui_settings/ui_settings_client.ts +++ b/src/core/public/ui_settings/ui_settings_client.ts @@ -21,18 +21,25 @@ import { cloneDeep, defaultsDeep } from 'lodash'; import * as Rx from 'rxjs'; import { filter, map } from 'rxjs/operators'; +import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types'; import { UiSettingsState } from './types'; + import { UiSettingsApi } from './ui_settings_api'; /** @public */ interface UiSettingsClientParams { api: UiSettingsApi; - defaults: UiSettingsState; + defaults: Record; initialSettings?: UiSettingsState; } /** + * Client-side client that provides access to the advanced settings stored in elasticsearch. + * The settings provide control over the behavior of the Kibana application. + * For example, a user can specify how to display numeric or date fields. + * Users can adjust the settings via Management UI. * {@link UiSettingsClient} + * * @public */ export type UiSettingsClientContract = PublicMethodsOf; @@ -44,8 +51,8 @@ export class UiSettingsClient { private readonly updateErrors$ = new Rx.Subject(); private readonly api: UiSettingsApi; - private readonly defaults: UiSettingsState; - private cache: UiSettingsState; + private readonly defaults: Record; + private cache: Record; constructor(params: UiSettingsClientParams) { this.api = params.api; diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 1a6a8929cd6a0b..383ba77f177799 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -22,32 +22,33 @@ import { ElasticsearchConfig, config } from './elasticsearch_config'; test('set correct defaults', () => { const configValue = new ElasticsearchConfig(config.schema.validate({})); expect(configValue).toMatchInlineSnapshot(` -ElasticsearchConfig { - "apiVersion": "master", - "customHeaders": Object {}, - "healthCheckDelay": "PT2.5S", - "hosts": Array [ - "http://localhost:9200", - ], - "logQueries": false, - "password": undefined, - "pingTimeout": "PT30S", - "requestHeadersWhitelist": Array [ - "authorization", - ], - "requestTimeout": "PT30S", - "shardTimeout": "PT30S", - "sniffInterval": false, - "sniffOnConnectionFault": false, - "sniffOnStart": false, - "ssl": Object { - "alwaysPresentCertificate": true, - "certificateAuthorities": undefined, - "verificationMode": "full", - }, - "username": undefined, -} -`); + ElasticsearchConfig { + "apiVersion": "master", + "customHeaders": Object {}, + "healthCheckDelay": "PT2.5S", + "hosts": Array [ + "http://localhost:9200", + ], + "ignoreVersionMismatch": false, + "logQueries": false, + "password": undefined, + "pingTimeout": "PT30S", + "requestHeadersWhitelist": Array [ + "authorization", + ], + "requestTimeout": "PT30S", + "shardTimeout": "PT30S", + "sniffInterval": false, + "sniffOnConnectionFault": false, + "sniffOnStart": false, + "ssl": Object { + "alwaysPresentCertificate": true, + "certificateAuthorities": undefined, + "verificationMode": "full", + }, + "username": undefined, + } + `); }); test('#hosts accepts both string and array of strings', () => { diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index fb585a8d672627..947a0d27546b12 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -65,6 +65,7 @@ export const config = { }), apiVersion: schema.string({ defaultValue: DEFAULT_API_VERSION }), healthCheck: schema.object({ delay: schema.duration({ defaultValue: 2500 }) }), + ignoreVersionMismatch: schema.boolean({ defaultValue: false }), }), }; @@ -74,6 +75,11 @@ export class ElasticsearchConfig { */ public readonly healthCheckDelay: Duration; + /** + * Whether to allow kibana to connect to a non-compatible elasticsearch node. + */ + public readonly ignoreVersionMismatch: boolean; + /** * Version of the Elasticsearch (6.7, 7.1 or `master`) client will be connecting to. */ @@ -161,6 +167,7 @@ export class ElasticsearchConfig { public readonly customHeaders: ElasticsearchConfigType['customHeaders']; constructor(rawConfig: ElasticsearchConfigType) { + this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch; this.apiVersion = rawConfig.apiVersion; this.logQueries = rawConfig.logQueries; this.hosts = Array.isArray(rawConfig.hosts) ? rawConfig.hosts : [rawConfig.hosts]; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 58fe2b52727efd..6c4a1f263bc713 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -174,11 +174,55 @@ Object { undefined, ], "ssl": Object { + "certificateAuthorities": undefined, "verificationMode": "none", }, } `); }); + + it('does not merge elasticsearch hosts if custom config overrides', async () => { + configService.atPath.mockReturnValueOnce( + new BehaviorSubject({ + hosts: ['http://1.2.3.4', 'http://9.8.7.6'], + healthCheck: { + delay: 2000, + }, + ssl: { + verificationMode: 'none', + }, + } as any) + ); + elasticsearchService = new ElasticsearchService(coreContext); + const setupContract = await elasticsearchService.setup(deps); + // reset all mocks called during setup phase + MockClusterClient.mockClear(); + + const customConfig = { + hosts: ['http://8.8.8.8'], + logQueries: true, + ssl: { certificate: 'certificate-value' }, + }; + setupContract.createClient('some-custom-type', customConfig); + + const config = MockClusterClient.mock.calls[0][0]; + expect(config).toMatchInlineSnapshot(` + Object { + "healthCheckDelay": 2000, + "hosts": Array [ + "http://8.8.8.8", + ], + "logQueries": true, + "requestHeadersWhitelist": Array [ + undefined, + ], + "ssl": Object { + "certificate": "certificate-value", + "verificationMode": "none", + }, + } + `); + }); }); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index ed1f2a276ebc87..1f062412edaf22 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -19,8 +19,9 @@ import { ConnectableObservable, Observable, Subscription } from 'rxjs'; import { filter, first, map, publishReplay, switchMap } from 'rxjs/operators'; -import { merge } from 'lodash'; + import { CoreService } from '../../types'; +import { merge } from '../../utils'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { ClusterClient } from './cluster_client'; diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index f61371c5437e6b..acae9d8ff0e704 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -577,45 +577,6 @@ test('exposes route details of incoming request to a route handler', async () => }); }); -describe('conditional compression', () => { - test('disables compression when there is a referer', async () => { - const { registerRouter, server: innerServer } = await server.setup(config); - - const router = new Router('', logger, enhanceWithContext); - router.get({ path: '/', validate: false }, (context, req, res) => - // we need the large body here so that compression would normally be used - res.ok({ body: 'hello'.repeat(500), headers: { 'Content-Type': 'text/html; charset=UTF-8' } }) - ); - registerRouter(router); - - await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .set('accept-encoding', 'gzip') - .set('referer', 'http://some-other-site/'); - - expect(response.header).not.toHaveProperty('content-encoding'); - }); - - test(`enables compression when there isn't a referer`, async () => { - const { registerRouter, server: innerServer } = await server.setup(config); - - const router = new Router('', logger, enhanceWithContext); - router.get({ path: '/', validate: false }, (context, req, res) => - // we need the large body here so that compression will be used - res.ok({ body: 'hello'.repeat(500), headers: { 'Content-Type': 'text/html; charset=UTF-8' } }) - ); - registerRouter(router); - - await server.start(); - const response = await supertest(innerServer.listener) - .get('/') - .set('accept-encoding', 'gzip'); - - expect(response.header).toHaveProperty('content-encoding', 'gzip'); - }); -}); - describe('setup contract', () => { describe('#createSessionStorage', () => { it('creates session storage factory', async () => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index d6077200d3c757..3354324c12407d 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -96,7 +96,6 @@ export class HttpServer { const basePathService = new BasePath(config.basePath); this.setupBasePathRewrite(config, basePathService); - this.setupConditionalCompression(); return { registerRouter: this.registerRouter.bind(this), @@ -176,23 +175,6 @@ export class HttpServer { }); } - private setupConditionalCompression() { - if (this.server === undefined) { - throw new Error('Server is not created yet'); - } - - this.server.ext('onRequest', (request, h) => { - // whenever there is a referrer, don't use compression even if the client supports it - if (request.info.referrer !== '') { - this.log.debug( - `Not using compression because there is a referer: ${request.info.referrer}` - ); - request.info.acceptEncoding = ''; - } - return h.continue; - }); - } - private registerOnPostAuth(fn: OnPostAuthHandler) { if (this.server === undefined) { throw new Error('Server is not created yet'); diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index fe6cb32d217760..e5dabc99f44426 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -120,7 +120,11 @@ export class HapiResponseAdapter { }); error.output.payload.message = getErrorMessage(payload); - error.output.payload.attributes = getErrorAttributes(payload); + + const attributes = getErrorAttributes(payload); + if (attributes) { + error.output.payload.attributes = attributes; + } const headers = kibanaResponse.options.headers; if (headers) { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e0d230006d5874..35e83da4ef30c8 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -49,7 +49,11 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; -import { InternalUiSettingsServiceSetup } from './ui_settings'; +import { + InternalUiSettingsServiceSetup, + IUiSettingsClient, + UiSettingsServiceSetup, +} from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; export { bootstrap } from './bootstrap'; @@ -139,6 +143,7 @@ export { SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, SavedObjectsBulkUpdateObject, + SavedObjectsBulkUpdateOptions, SavedObjectsBulkResponse, SavedObjectsBulkUpdateResponse, SavedObjectsClient, @@ -166,6 +171,7 @@ export { SavedObjectsLegacyService, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, + SavedObjectsDeleteOptions, } from './saved_objects'; export { @@ -173,6 +179,8 @@ export { UiSettingsParams, InternalUiSettingsServiceSetup, UiSettingsType, + UiSettingsServiceSetup, + UserProvidedValues, } from './ui_settings'; export { RecursiveReadonly } from '../utils'; @@ -184,6 +192,7 @@ export { SavedObjectAttributeSingle, SavedObjectReference, SavedObjectsBaseOptions, + MutatingOperationRefreshSetting, SavedObjectsClientContract, SavedObjectsFindOptions, SavedObjectsMigrationVersion, @@ -213,6 +222,9 @@ export interface RequestHandlerContext { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; }; + uiSettings: { + client: IUiSettingsClient; + }; }; } @@ -228,6 +240,8 @@ export interface CoreSetup { elasticsearch: ElasticsearchServiceSetup; /** {@link HttpServiceSetup} */ http: HttpServiceSetup; + /** {@link UiSettingsServiceSetup} */ + uiSettings: UiSettingsServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 590bd192e3ded3..e2aefd846d9788 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -72,7 +72,7 @@ beforeEach(() => { core: { context: contextServiceMock.createSetupContract(), elasticsearch: { legacy: {} } as any, - uiSettings: uiSettingsServiceMock.createSetup(), + uiSettings: uiSettingsServiceMock.createSetupContract(), http: { ...httpServiceMock.createSetupContract(), auth: { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index dd3ee3db89153a..b7c55a8af7c18d 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -248,6 +248,9 @@ export class LegacyService implements CoreService { basePath: setupDeps.core.http.basePath, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, + uiSettings: { + register: setupDeps.core.uiSettings.register, + }, }; const coreStart: CoreStart = {}; diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index f1f4da8d0b4d75..c0a6026708af3a 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -54,7 +54,7 @@ export async function findLegacyPluginSpecs(settings: unknown, loggerFactory: Lo invalidDirectoryError$: Observable<{ path: string }>; invalidPackError$: Observable<{ path: string }>; otherError$: Observable; - deprecation$: Observable; + deprecation$: Observable<{ spec: LegacyPluginSpec; message: string }>; invalidVersionSpec$: Observable; spec$: Observable; disabledSpec$: Observable; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index fb703c6c350081..b51d5302e32746 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -22,6 +22,7 @@ import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -29,7 +30,7 @@ export { configServiceMock } from './config/config_service.mock'; export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; -export { SavedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; +export { savedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export function pluginInitializerContextConfigMock(config: T) { @@ -79,10 +80,14 @@ function createCoreSetupMock() { }; httpMock.createRouter.mockImplementation(() => httpService.createRouter('')); + const uiSettingsMock = { + register: uiSettingsServiceMock.createSetupContract().register, + }; const mock: MockedKeys = { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpMock, + uiSettings: uiSettingsMock, }; return mock; @@ -94,8 +99,19 @@ function createCoreStartMock() { return mock; } +function createInternalCoreSetupMock() { + const setupDeps = { + context: contextServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetupContract(), + http: httpServiceMock.createSetupContract(), + uiSettings: uiSettingsServiceMock.createSetupContract(), + }; + return setupDeps; +} + export const coreMock = { createSetup: createCoreSetupMock, createStart: createCoreStartMock, + createInternalSetup: createInternalCoreSetupMock, createPluginInitializerContext: pluginInitializerContextMock, }; diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index 74e9dd709bb238..521d02e487df61 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -29,7 +29,7 @@ import { PluginsConfig } from '../plugins_config'; import { PluginDiscoveryError } from './plugin_discovery_error'; import { parseManifest } from './plugin_manifest_parser'; -const fsReadDir$ = bindNodeCallback(readdir); +const fsReadDir$ = bindNodeCallback(readdir); const fsStat$ = bindNodeCallback(stat); /** diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 5c57a5fa2c8d17..e457f01a1941c3 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -24,15 +24,13 @@ import { schema } from '@kbn/config-schema'; import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; +import { coreMock } from '../mocks'; import { configServiceMock } from '../config/config_service.mock'; -import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginWrapper } from './plugin'; import { PluginManifest } from './types'; import { createPluginInitializerContext, createPluginSetupContext } from './plugin_context'; -import { contextServiceMock } from '../context/context_service.mock'; const mockPluginInitializer = jest.fn(); const logger = loggingServiceMock.create(); @@ -68,11 +66,7 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let coreId: symbol; let env: Env; let coreContext: CoreContext; -const setupDeps = { - context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), -}; +const setupDeps = coreMock.createInternalSetup(); beforeEach(() => { coreId = Symbol('core'); env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index edcafbb9a3dc33..9885a572ad8c00 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -123,6 +123,9 @@ export function createPluginSetupContext( basePath: deps.http.basePath, isTlsEnabled: deps.http.isTlsEnabled, }, + uiSettings: { + register: deps.uiSettings.register, + }, }; } diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index d25f9087432bd9..0b3bc0759463c1 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -25,15 +25,13 @@ import { schema } from '@kbn/config-schema'; import { Config, ConfigPath, ConfigService, Env, ObjectToConfigAdapter } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; -import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; +import { coreMock } from '../mocks'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginDiscoveryError } from './discovery'; import { PluginWrapper } from './plugin'; import { PluginsService } from './plugins_service'; import { PluginsSystem } from './plugins_system'; import { config } from './plugins_config'; -import { contextServiceMock } from '../context/context_service.mock'; const MockPluginsSystem: jest.Mock = PluginsSystem as any; @@ -42,11 +40,7 @@ let configService: ConfigService; let coreId: symbol; let env: Env; let mockPluginSystem: jest.Mocked; -const setupDeps = { - context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), -}; +const setupDeps = coreMock.createInternalSetup(); const logger = loggingServiceMock.create(); ['path-1', 'path-2', 'path-3', 'path-4', 'path-5'].forEach(path => { diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 92b537deae3371..2964e34c370b1c 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -21,15 +21,14 @@ import { Observable } from 'rxjs'; import { filter, first, map, mergeMap, tap, toArray } from 'rxjs/operators'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; -import { InternalElasticsearchServiceSetup } from '../elasticsearch'; -import { InternalHttpServiceSetup } from '../http'; + import { Logger } from '../logging'; import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery'; import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, DiscoveredPluginInternal, PluginName } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; -import { ContextSetup } from '../context'; +import { InternalCoreSetup } from '..'; /** @public */ export interface PluginsServiceSetup { @@ -46,11 +45,7 @@ export interface PluginsServiceStart { } /** @internal */ -export interface PluginsServiceSetupDeps { - context: ContextSetup; - elasticsearch: InternalElasticsearchServiceSetup; - http: InternalHttpServiceSetup; -} +export type PluginsServiceSetupDeps = InternalCoreSetup; /** @internal */ export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index f67bd371ff5651..efd53681d6cd0d 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -28,13 +28,13 @@ import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; -import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; + import { PluginWrapper } from './plugin'; import { PluginName } from './types'; import { PluginsSystem } from './plugins_system'; -import { contextServiceMock } from '../context/context_service.mock'; + +import { coreMock } from '../mocks'; const logger = loggingServiceMock.create(); function createPlugin( @@ -68,11 +68,8 @@ const configService = configServiceMock.create(); configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let env: Env; let coreContext: CoreContext; -const setupDeps = { - context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), -}; +const setupDeps = coreMock.createInternalSetup(); + beforeEach(() => { env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index 1a2a843ebb2b89..9a3449b65a9412 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -18,7 +18,7 @@ */ import { getSortedObjectsForExport } from './get_sorted_objects_for_export'; -import { SavedObjectsClientMock } from '../service/saved_objects_client.mock'; +import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '../../../../legacy/utils/streams'; @@ -27,7 +27,7 @@ async function readStreamToCompletion(stream: Readable) { } describe('getSortedObjectsForExport()', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); afterEach(() => { savedObjectsClient.find.mockReset(); diff --git a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts index 57feebbf67ccd0..a571f62e3d1c14 100644 --- a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts +++ b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts @@ -18,7 +18,7 @@ */ import { SavedObject } from '../types'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; import { getObjectReferencesToFetch, fetchNestedDependencies } from './inject_nested_depdendencies'; describe('getObjectReferencesToFetch()', () => { @@ -109,7 +109,7 @@ describe('getObjectReferencesToFetch()', () => { }); describe('injectNestedDependencies', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); afterEach(() => { jest.resetAllMocks(); diff --git a/src/core/server/saved_objects/import/import_saved_objects.test.ts b/src/core/server/saved_objects/import/import_saved_objects.test.ts index df95fb75f0f4fb..f0719cbf4c829b 100644 --- a/src/core/server/saved_objects/import/import_saved_objects.test.ts +++ b/src/core/server/saved_objects/import/import_saved_objects.test.ts @@ -20,7 +20,7 @@ import { Readable } from 'stream'; import { SavedObject } from '../types'; import { importSavedObjects } from './import_saved_objects'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; const emptyResponse = { saved_objects: [], @@ -63,7 +63,7 @@ describe('importSavedObjects()', () => { references: [], }, ]; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); diff --git a/src/core/server/saved_objects/import/resolve_import_errors.test.ts b/src/core/server/saved_objects/import/resolve_import_errors.test.ts index 6aab8ef5adf9e3..c522d76f1ff041 100644 --- a/src/core/server/saved_objects/import/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/import/resolve_import_errors.test.ts @@ -20,7 +20,7 @@ import { Readable } from 'stream'; import { SavedObject } from '../types'; import { resolveImportErrors } from './resolve_import_errors'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; describe('resolveImportErrors()', () => { const savedObjects: SavedObject[] = [ @@ -63,7 +63,7 @@ describe('resolveImportErrors()', () => { ], }, ]; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); diff --git a/src/core/server/saved_objects/import/validate_references.test.ts b/src/core/server/saved_objects/import/validate_references.test.ts index 269cd3055b0470..6642cf149eda96 100644 --- a/src/core/server/saved_objects/import/validate_references.test.ts +++ b/src/core/server/saved_objects/import/validate_references.test.ts @@ -18,10 +18,10 @@ */ import { getNonExistingReferenceAsKeys, validateReferences } from './validate_references'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; describe('getNonExistingReferenceAsKeys()', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); @@ -222,7 +222,7 @@ describe('getNonExistingReferenceAsKeys()', () => { }); describe('validateReferences()', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 9e4edcd8d29437..6525590ee96c5b 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -417,6 +417,32 @@ describe('SavedObjectsRepository', () => { }); }); + it('defaults to a refresh setting of `wait_for`', async () => { + await savedObjectsRepository.create('index-pattern', { + id: 'logstash-*', + title: 'Logstash', + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts custom refresh settings', async () => { + await savedObjectsRepository.create('index-pattern', { + id: 'logstash-*', + title: 'Logstash', + }, { + refresh: true + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); + it('should use create action if ID defined and overwrite=false', async () => { await savedObjectsRepository.create( 'index-pattern', @@ -546,6 +572,28 @@ describe('SavedObjectsRepository', () => { ); expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + + it('defaults to empty references array if none are provided', async () => { + await savedObjectsRepository.create( + 'index-pattern', + { + title: 'Logstash', + }, + { + id: 'logstash-*', + } + ); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: expect.objectContaining({ + references: [], + }), + }) + ); + }); }); describe('#bulkCreate', () => { @@ -623,6 +671,61 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + it('defaults to a refresh setting of `wait_for`', async () => { + callAdminCluster.mockReturnValue({ + items: [ + { create: { type: 'config', id: 'config:one', _primary_term: 1, _seq_no: 1 } }, + ], + }); + + await savedObjectsRepository.bulkCreate([ + { + type: 'config', + id: 'one', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + } + ]); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts a custom refresh setting', async () => { + callAdminCluster.mockReturnValue({ + items: [ + { create: { type: 'config', id: 'config:one', _primary_term: 1, _seq_no: 1 } }, + { create: { type: 'index-pattern', id: 'config:two', _primary_term: 1, _seq_no: 1 } }, + ], + }); + + await savedObjectsRepository.bulkCreate([ + { + type: 'config', + id: 'one', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }, + { + type: 'index-pattern', + id: 'two', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }, + ], { + refresh: true + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); + it('migrates the docs', async () => { callAdminCluster.mockReturnValue({ items: [ @@ -1065,6 +1168,28 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + + it('defaults to a refresh setting of `wait_for`', async () => { + callAdminCluster.mockReturnValue({ result: 'deleted' }); + await savedObjectsRepository.delete('globaltype', 'logstash-*'); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it(`accepts a custom refresh setting`, async () => { + callAdminCluster.mockReturnValue({ result: 'deleted' }); + await savedObjectsRepository.delete('globaltype', 'logstash-*', { + refresh: false + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: false, + }); + }); }); describe('#deleteByNamespace', () => { @@ -1104,6 +1229,26 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', }); }); + + it('defaults to a refresh setting of `wait_for`', async () => { + callAdminCluster.mockReturnValue(deleteByQueryResults); + await savedObjectsRepository.deleteByNamespace('my-namespace'); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for', + }); + }); + + it('accepts a custom refresh setting', async () => { + callAdminCluster.mockReturnValue(deleteByQueryResults); + await savedObjectsRepository.deleteByNamespace('my-namespace', { refresh: true }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true, + }); + }); }); describe('#find', () => { @@ -1232,7 +1377,7 @@ describe('SavedObjectsRepository', () => { }; await expect(savedObjectsRepository.find(findOpts)).rejects.toMatchInlineSnapshot(` - [Error: KQLSyntaxError: Expected "(", value, whitespace but "<" found. + [Error: KQLSyntaxError: Expected "(", "{", value, whitespace but "<" found. dashboard.attributes.otherField:< --------------------------------^: Bad Request] `); @@ -1962,6 +2107,40 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + + it('defaults to a refresh setting of `wait_for`', async () => { + await savedObjectsRepository.update( + 'globaltype', + 'foo', + { + name: 'bar', + } + ); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts a custom refresh setting', async () => { + await savedObjectsRepository.update( + 'globaltype', + 'foo', + { + name: 'bar', + }, + { + refresh: true, + namespace: 'foo-namespace', + } + ); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); }); describe('#bulkUpdate', () => { @@ -2262,6 +2441,44 @@ describe('SavedObjectsRepository', () => { }); }); + it('defaults to a refresh setting of `wait_for`', async () => { + const objects = [ + { + type: 'index-pattern', + id: `logstash-no-ref`, + attributes: { title: `Testing no-ref` }, + references: [] + } + ]; + + mockValidResponse(objects); + + await savedObjectsRepository.bulkUpdate(objects); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ refresh: 'wait_for' }); + }); + + it('accepts a custom refresh setting', async () => { + const objects = [ + { + type: 'index-pattern', + id: `logstash-no-ref`, + attributes: { title: `Testing no-ref` }, + references: [] + } + ]; + + mockValidResponse(objects); + + await savedObjectsRepository.bulkUpdate(objects, { refresh: true }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ refresh: true }); + }); + it(`prepends namespace to the id but doesn't add namespace to body when providing namespace for namespaced type`, async () => { const objects = [ @@ -2504,6 +2721,29 @@ describe('SavedObjectsRepository', () => { }); }); + it('defaults to a refresh setting of `wait_for`', async () => { + await savedObjectsRepository.incrementCounter('config', 'doesnotexist', 'buildNum', { + namespace: 'foo-namespace' + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts a custom refresh setting', async () => { + await savedObjectsRepository.incrementCounter('config', 'doesnotexist', 'buildNum', { + namespace: 'foo-namespace', + refresh: true + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); + it(`prepends namespace to the id but doesn't add namespace to body when providing namespace for namespaced type`, async () => { await savedObjectsRepository.incrementCounter('config', '6.0.0-alpha1', 'buildNum', { namespace: 'foo-namespace', diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 898239e5736de3..54b9938decb0a9 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -40,6 +40,9 @@ import { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, SavedObjectsBulkUpdateObject, + SavedObjectsBulkUpdateOptions, + SavedObjectsDeleteOptions, + SavedObjectsDeleteByNamespaceOptions, } from '../saved_objects_client'; import { SavedObject, @@ -47,6 +50,7 @@ import { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsMigrationVersion, + MutatingOperationRefreshSetting, } from '../../types'; import { validateConvertFilterToKueryNode } from './filter_utils'; @@ -83,8 +87,12 @@ export interface SavedObjectsRepositoryOptions { export interface IncrementCounterOptions extends SavedObjectsBaseOptions { migrationVersion?: SavedObjectsMigrationVersion; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; } +const DEFAULT_REFRESH_SETTING = 'wait_for'; + export class SavedObjectsRepository { private _migrator: KibanaMigrator; private _index: string; @@ -146,15 +154,22 @@ export class SavedObjectsRepository { * @property {boolean} [options.overwrite=false] * @property {object} [options.migrationVersion=undefined] * @property {string} [options.namespace] - * @property {array} [options.references] - [{ name, type, id }] + * @property {array} [options.references=[]] - [{ name, type, id }] * @returns {promise} - { id, type, version, attributes } */ public async create( type: string, attributes: T, - options: SavedObjectsCreateOptions = { overwrite: false, references: [] } + options: SavedObjectsCreateOptions = {} ): Promise> { - const { id, migrationVersion, overwrite, namespace, references } = options; + const { + id, + migrationVersion, + overwrite = false, + namespace, + references = [], + refresh = DEFAULT_REFRESH_SETTING, + } = options; if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); @@ -179,7 +194,7 @@ export class SavedObjectsRepository { const response = await this._writeToCluster(method, { id: raw._id, index: this.getIndexForType(type), - refresh: 'wait_for', + refresh, body: raw._source, }); @@ -210,7 +225,7 @@ export class SavedObjectsRepository { objects: Array>, options: SavedObjectsCreateOptions = {} ): Promise> { - const { namespace, overwrite = false } = options; + const { namespace, overwrite = false, refresh = DEFAULT_REFRESH_SETTING } = options; const time = this._getCurrentTime(); const bulkCreateParams: object[] = []; @@ -256,7 +271,7 @@ export class SavedObjectsRepository { }); const esResponse = await this._writeToCluster('bulk', { - refresh: 'wait_for', + refresh, body: bulkCreateParams, }); @@ -308,17 +323,17 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} */ - async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}): Promise<{}> { + async delete(type: string, id: string, options: SavedObjectsDeleteOptions = {}): Promise<{}> { if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(); } - const { namespace } = options; + const { namespace, refresh = DEFAULT_REFRESH_SETTING } = options; const response = await this._writeToCluster('delete', { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), - refresh: 'wait_for', + refresh, ignore: [404], }); @@ -345,11 +360,16 @@ export class SavedObjectsRepository { * @param {string} namespace * @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures } */ - async deleteByNamespace(namespace: string): Promise { + async deleteByNamespace( + namespace: string, + options: SavedObjectsDeleteByNamespaceOptions = {} + ): Promise { if (!namespace || typeof namespace !== 'string') { throw new TypeError(`namespace is required, and must be a string`); } + const { refresh = DEFAULT_REFRESH_SETTING } = options; + const allTypes = Object.keys(getRootPropertiesObjects(this._mappings)); const typesToDelete = allTypes.filter(type => !this._schema.isNamespaceAgnostic(type)); @@ -357,7 +377,7 @@ export class SavedObjectsRepository { const esOptions = { index: this.getIndicesForTypes(typesToDelete), ignore: [404], - refresh: 'wait_for', + refresh, body: { conflicts: 'proceed', ...getSearchDsl(this._mappings, this._schema, { @@ -626,7 +646,7 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { version, namespace, references } = options; + const { version, namespace, references, refresh = DEFAULT_REFRESH_SETTING } = options; const time = this._getCurrentTime(); @@ -643,7 +663,7 @@ export class SavedObjectsRepository { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), ...(version && decodeRequestVersion(version)), - refresh: 'wait_for', + refresh, ignore: [404], body: { doc, @@ -675,7 +695,7 @@ export class SavedObjectsRepository { */ async bulkUpdate( objects: Array>, - options: SavedObjectsBaseOptions = {} + options: SavedObjectsBulkUpdateOptions = {} ): Promise> { const time = this._getCurrentTime(); const bulkUpdateParams: object[] = []; @@ -729,9 +749,10 @@ export class SavedObjectsRepository { return { tag: 'Right' as 'Right', value: expectedResult }; }); + const { refresh = DEFAULT_REFRESH_SETTING } = options; const esResponse = bulkUpdateParams.length ? await this._writeToCluster('bulk', { - refresh: 'wait_for', + refresh, body: bulkUpdateParams, }) : {}; @@ -794,7 +815,7 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } - const { migrationVersion, namespace } = options; + const { migrationVersion, namespace, refresh = DEFAULT_REFRESH_SETTING } = options; const time = this._getCurrentTime(); @@ -811,7 +832,7 @@ export class SavedObjectsRepository { const response = await this._writeToCluster('update', { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), - refresh: 'wait_for', + refresh, _source: true, body: { script: { diff --git a/src/core/server/saved_objects/service/saved_objects_client.mock.ts b/src/core/server/saved_objects/service/saved_objects_client.mock.ts index 63c9a0ee35ae0d..c6de9fa94291c6 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.mock.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.mock.ts @@ -33,4 +33,4 @@ const create = () => update: jest.fn(), } as unknown) as jest.Mocked); -export const SavedObjectsClientMock = { create }; +export const savedObjectsClientMock = { create }; diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 4e04a08bd5212a..550e8a1de0d80b 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -24,6 +24,7 @@ import { SavedObjectReference, SavedObjectsMigrationVersion, SavedObjectsBaseOptions, + MutatingOperationRefreshSetting, SavedObjectsFindOptions, } from '../types'; import { SavedObjectsErrorHelpers } from './lib/errors'; @@ -40,6 +41,8 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; references?: SavedObjectReference[]; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; } /** @@ -101,6 +104,35 @@ export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { version?: string; /** {@inheritdoc SavedObjectReference} */ references?: SavedObjectReference[]; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + +/** + * + * @public + */ +export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + +/** + * + * @public + */ +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + +/** + * + * @public + */ +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; } /** @@ -189,7 +221,7 @@ export class SavedObjectsClient { * @param id * @param options */ - async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + async delete(type: string, id: string, options: SavedObjectsDeleteOptions = {}) { return await this._repository.delete(type, id, options); } @@ -260,7 +292,7 @@ export class SavedObjectsClient { */ async bulkUpdate( objects: Array>, - options?: SavedObjectsBaseOptions + options?: SavedObjectsBulkUpdateOptions ): Promise> { return await this._repository.bulkUpdate(objects, options); } diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index a968b6d9392f88..2c6f5e4a520a7f 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -142,6 +142,12 @@ export interface SavedObjectsBaseOptions { namespace?: string; } +/** + * Elasticsearch Refresh setting for mutating operation + * @public + */ +export type MutatingOperationRefreshSetting = boolean | 'wait_for'; + /** * Saved Objects is Kibana's data persisentence mechanism allowing plugins to * use Elasticsearch for storing plugin state. diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 9740f1f7032d1c..14943fc96f2681 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -511,6 +511,8 @@ export interface CoreSetup { elasticsearch: ElasticsearchServiceSetup; // (undocumented) http: HttpServiceSetup; + // (undocumented) + uiSettings: UiSettingsServiceSetup; } // @public @@ -737,7 +739,7 @@ export interface InternalCoreStart { // @internal (undocumented) export interface InternalUiSettingsServiceSetup { asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; - setDefaults(values: Record): void; + register(settings: Record): void; } // @public @@ -763,11 +765,8 @@ export type IScopedClusterClient = Pick(key: string) => Promise; getAll: () => Promise>; - getDefaults: () => Record; - getUserProvided: () => Promise>; + getRegistered: () => Readonly>; + getUserProvided: () => Promise>>; isOverridden: (key: string) => boolean; remove: (key: string) => Promise; removeMany: (keys: string[]) => Promise; @@ -942,6 +941,9 @@ export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; // @public (undocumented) export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; +// @public +export type MutatingOperationRefreshSetting = boolean | 'wait_for'; + // Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts // // @public @@ -1071,6 +1073,9 @@ export interface RequestHandlerContext { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; }; + uiSettings: { + client: IUiSettingsClient; + }; }; } @@ -1196,6 +1201,11 @@ export interface SavedObjectsBulkUpdateObject { // (undocumented) @@ -1208,9 +1218,9 @@ export class SavedObjectsClient { constructor(repository: SavedObjectsRepository); bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}>; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; // (undocumented) errors: typeof SavedObjectsErrorHelpers; // (undocumented) @@ -1247,6 +1257,12 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { overwrite?: boolean; // (undocumented) references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; } // @public (undocumented) @@ -1554,6 +1570,7 @@ export class SavedObjectsSerializer { // @public (undocumented) export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; version?: string; } @@ -1595,24 +1612,37 @@ export interface SessionStorageFactory { // @public export interface UiSettingsParams { - category: string[]; - description: string; - name: string; + category?: string[]; + description?: string; + name?: string; optionLabels?: Record; options?: string[]; readonly?: boolean; requiresPageReload?: boolean; type?: UiSettingsType; - value: SavedObjectAttribute; + value?: SavedObjectAttribute; +} + +// @public (undocumented) +export interface UiSettingsServiceSetup { + register(settings: Record): void; } // @public export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; +// @public +export interface UserProvidedValues { + // (undocumented) + isOverridden?: boolean; + // (undocumented) + userValue?: T; +} + // Warnings were encountered during analysis: // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/plugins_service.ts:39:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/plugins_service.ts:38:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 49136c8e09768c..46974e204c7a42 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -21,7 +21,7 @@ import { take } from 'rxjs/operators'; import { Type } from '@kbn/config-schema'; import { ConfigService, Env, Config, ConfigPath } from './config'; -import { ElasticsearchService, ElasticsearchServiceSetup } from './elasticsearch'; +import { ElasticsearchService } from './elasticsearch'; import { HttpService, InternalHttpServiceSetup } from './http'; import { LegacyService } from './legacy'; import { Logger, LoggerFactory } from './logging'; @@ -39,7 +39,7 @@ import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; -import { RequestHandlerContext } from '.'; +import { RequestHandlerContext, InternalCoreSetup } from '.'; const coreId = Symbol('core'); @@ -102,7 +102,7 @@ export class Server { http: httpSetup, }); - const coreSetup = { + const coreSetup: InternalCoreSetup = { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, @@ -121,7 +121,7 @@ export class Server { legacy: legacySetup, }); - this.registerCoreContext({ ...coreSetup, savedObjects: savedObjectsSetup }); + this.registerCoreContext(coreSetup, savedObjectsSetup); return coreSetup; } @@ -163,27 +163,31 @@ export class Server { ); } - private registerCoreContext(coreSetup: { - http: InternalHttpServiceSetup; - elasticsearch: ElasticsearchServiceSetup; - savedObjects: SavedObjectsServiceSetup; - }) { + private registerCoreContext( + coreSetup: InternalCoreSetup, + savedObjects: SavedObjectsServiceSetup + ) { coreSetup.http.registerRouteHandlerContext( coreId, 'core', async (context, req): Promise => { const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + const savedObjectsClient = savedObjects.clientProvider.getClient(req); + return { savedObjects: { // Note: the client provider doesn't support new ES clients // emitted from adminClient$ - client: coreSetup.savedObjects.clientProvider.getClient(req), + client: savedObjectsClient, }, elasticsearch: { adminClient: adminClient.asScoped(req), dataClient: dataClient.asScoped(req), }, + uiSettings: { + client: coreSetup.uiSettings.asScopedToClient(savedObjectsClient), + }, }; } ); diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 46c70a91721b59..4878fb9ccae19c 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -20,4 +20,5 @@ /** This module is intended for consumption by public to avoid import issues with server-side code */ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; +export * from './ui_settings/types'; export { EnvironmentMode, PackageInfo } from './config/types'; diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 5f7e9153658736..65b8792532acf5 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -20,6 +20,8 @@ import sinon from 'sinon'; import Chance from 'chance'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; + import { loggingServiceMock } from '../../logging/logging_service.mock'; import * as getUpgradeableConfigNS from './get_upgradeable_config'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; @@ -50,6 +52,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { version, buildNum, log: logger.get(), + handleWriteErrors: false, ...options, }); @@ -173,85 +176,64 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); }); - describe('onWriteError()', () => { - it('is called with error and attributes when savedObjectsClient.create rejects', async () => { - const { run, savedObjectsClient } = setup(); + describe('handleWriteErrors', () => { + describe('handleWriteErrors: false', () => { + it('throws write errors', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.callsFake(async () => { + throw error; + }); - const error = new Error('foo'); - savedObjectsClient.create.callsFake(async () => { - throw error; - }); - - const onWriteError = sinon.stub(); - await run({ onWriteError }); - sinon.assert.calledOnce(onWriteError); - sinon.assert.calledWithExactly(onWriteError, error, { - buildNum, + await expect(run({ handleWriteErrors: false })).rejects.toThrowError(error); }); }); + describe('handleWriteErrors:true', () => { + it('returns undefined for ConflictError', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateConflictError(error)); - it('resolves with the return value of onWriteError()', async () => { - const { run, savedObjectsClient } = setup(); - - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + expect(await run({ handleWriteErrors: true })).toBe(undefined); }); - const result = await run({ onWriteError: () => 123 }); - expect(result).toBe(123); - }); - - it('rejects with the error from onWriteError() if it rejects', async () => { - const { run, savedObjectsClient } = setup(); + it('returns config attributes for NotAuthorizedError', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws( + SavedObjectsErrorHelpers.decorateNotAuthorizedError(error) + ); - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + expect(await run({ handleWriteErrors: true })).toEqual({ + buildNum, + }); }); - try { - await run({ - onWriteError: (error: Error) => Promise.reject(new Error(`${error.message} bar`)), + it('returns config attributes for ForbiddenError', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateForbiddenError(error)); + + expect(await run({ handleWriteErrors: true })).toEqual({ + buildNum, }); - throw new Error('expected run() to reject'); - } catch (error) { - expect(error.message).toBe('foo bar'); - } - }); + }); - it('rejects with the error from onWriteError() if it throws sync', async () => { - const { run, savedObjectsClient } = setup(); + it('throws error for other SavedObjects exceptions', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateGeneralError(error)); - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error); }); - try { - await run({ - onWriteError: (error: Error) => { - throw new Error(`${error.message} bar`); - }, - }); - throw new Error('expected run() to reject'); - } catch (error) { - expect(error.message).toBe('foo bar'); - } - }); + it('throws error for all other exceptions', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(error); - it('rejects with the writeError if onWriteError() is undefined', async () => { - const { run, savedObjectsClient } = setup(); - - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error); }); - - try { - await run({ - onWriteError: undefined, - }); - throw new Error('expected run() to reject'); - } catch (error) { - expect(error.message).toBe('foo'); - } }); }); }); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index 1655297adb6c9c..809e15248b5b0e 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -20,6 +20,7 @@ import { defaults } from 'lodash'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../../saved_objects/types'; +import { SavedObjectsErrorHelpers } from '../../saved_objects/'; import { Logger } from '../../logging'; import { getUpgradeableConfig } from './get_upgradeable_config'; @@ -29,15 +30,13 @@ interface Options { version: string; buildNum: number; log: Logger; - onWriteError?: ( - error: Error, - attributes: Record - ) => Record | undefined; + handleWriteErrors: boolean; } + export async function createOrUpgradeSavedConfig( options: Options ): Promise | undefined> { - const { savedObjectsClient, version, buildNum, log, onWriteError } = options; + const { savedObjectsClient, version, buildNum, log, handleWriteErrors } = options; // try to find an older config we can upgrade const upgradeableConfig = await getUpgradeableConfig({ @@ -52,8 +51,17 @@ export async function createOrUpgradeSavedConfig { + it('finds saved objects with type "config"', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [{ id: '7.5.0' }], + } as any); + + await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(savedObjectsClient.find.mock.calls[0][0].type).toBe('config'); + }); + + it('finds saved config with version < than Kibana version', async () => { + const savedConfig = { id: '7.4.0' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(savedConfig); + }); + + it('finds saved config with RC version === Kibana version', async () => { + const savedConfig = { id: '7.5.0-rc1' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(savedConfig); + }); + + it('does not find saved config with version === Kibana version', async () => { + const savedConfig = { id: '7.5.0' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(undefined); + }); + + it('does not find saved config with version > Kibana version', async () => { + const savedConfig = { id: '7.6.0' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(undefined); + }); + + it('handles empty config', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(undefined); + }); +}); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index 83f8ce03299f62..9d52a339ccf91b 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -18,28 +18,31 @@ */ import expect from '@kbn/expect'; -import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectsClientContract } from 'src/core/server'; -import KbnServer from '../../../../../legacy/server/kbn_server'; -import { createTestServers } from '../../../../../test_utils/kbn_server'; +import { + createTestServers, + TestElasticsearchUtils, + TestKibanaUtils, + TestUtils, +} from '../../../../../test_utils/kbn_server'; import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; const logger = loggingServiceMock.create().get(); describe('createOrUpgradeSavedConfig()', () => { let savedObjectsClient: SavedObjectsClientContract; - let kbnServer: KbnServer; - let servers: ReturnType; - let esServer: UnwrapPromise>; - let kbn: UnwrapPromise>; + let servers: TestUtils; + let esServer: TestElasticsearchUtils; + let kbn: TestKibanaUtils; + + let kbnServer: TestKibanaUtils['kbnServer']; beforeAll(async function() { servers = createTestServers({ adjustTimeout: t => { jest.setTimeout(t); }, - settings: {}, }); esServer = await servers.startES(); kbn = await servers.startKibana(); @@ -90,6 +93,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '5.4.0', buildNum: 54099, log: logger, + handleWriteErrors: false, }); const config540 = await savedObjectsClient.get('config', '5.4.0'); @@ -116,6 +120,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '5.4.1', buildNum: 54199, log: logger, + handleWriteErrors: false, }); const config541 = await savedObjectsClient.get('config', '5.4.1'); @@ -142,6 +147,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '7.0.0-rc1', buildNum: 70010, log: logger, + handleWriteErrors: false, }); const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1'); @@ -169,6 +175,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '7.0.0', buildNum: 70099, log: logger, + handleWriteErrors: false, }); const config700 = await savedObjectsClient.get('config', '7.0.0'); @@ -197,6 +204,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '6.2.3-rc1', buildNum: 62310, log: logger, + handleWriteErrors: false, }); const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1'); diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index edd0bfc4f3a89c..fd0a21bed4e129 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -17,16 +17,16 @@ * under the License. */ -export { - IUiSettingsClient, - UiSettingsClient, - UiSettingsServiceOptions, -} from './ui_settings_client'; +export { UiSettingsClient, UiSettingsServiceOptions } from './ui_settings_client'; export { config } from './ui_settings_config'; +export { UiSettingsService } from './ui_settings_service'; + export { + UiSettingsServiceSetup, + IUiSettingsClient, UiSettingsParams, - UiSettingsService, InternalUiSettingsServiceSetup, UiSettingsType, -} from './ui_settings_service'; + UserProvidedValues, +} from './types'; diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_exists.ts b/src/core/server/ui_settings/integration_tests/doc_exists.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_exists.ts rename to src/core/server/ui_settings/integration_tests/doc_exists.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_missing.ts b/src/core/server/ui_settings/integration_tests/doc_missing.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_missing.ts rename to src/core/server/ui_settings/integration_tests/doc_missing.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts b/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts rename to src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/index.test.ts b/src/core/server/ui_settings/integration_tests/index.test.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/index.test.ts rename to src/core/server/ui_settings/integration_tests/index.test.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/assert.ts b/src/core/server/ui_settings/integration_tests/lib/assert.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/assert.ts rename to src/core/server/ui_settings/integration_tests/lib/assert.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/chance.ts b/src/core/server/ui_settings/integration_tests/lib/chance.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/chance.ts rename to src/core/server/ui_settings/integration_tests/lib/chance.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/index.ts b/src/core/server/ui_settings/integration_tests/lib/index.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/index.ts rename to src/core/server/ui_settings/integration_tests/lib/index.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts similarity index 83% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts rename to src/core/server/ui_settings/integration_tests/lib/servers.ts index ae0ef1c91411e7..a1be1e7e7291e2 100644 --- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -17,20 +17,24 @@ * under the License. */ -import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/server'; -import KbnServer from '../../../../../server/kbn_server'; -import { createTestServers } from '../../../../../../test_utils/kbn_server'; -import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch'; +import { + createTestServers, + TestElasticsearchUtils, + TestKibanaUtils, + TestUtils, +} from '../../../../../test_utils/kbn_server'; +import { CallCluster } from '../../../../../legacy/core_plugins/elasticsearch'; -let kbnServer: KbnServer; -let servers: ReturnType; -let esServer: UnwrapPromise>; -let kbn: UnwrapPromise>; +let servers: TestUtils; +let esServer: TestElasticsearchUtils; +let kbn: TestKibanaUtils; + +let kbnServer: TestKibanaUtils['kbnServer']; interface AllServices { - kbnServer: KbnServer; + kbnServer: TestKibanaUtils['kbnServer']; savedObjectsClient: SavedObjectsClientContract; callCluster: CallCluster; uiSettings: IUiSettingsClient; diff --git a/src/core/server/ui_settings/routes/delete.ts b/src/core/server/ui_settings/routes/delete.ts new file mode 100644 index 00000000000000..ee4c05325fcd44 --- /dev/null +++ b/src/core/server/ui_settings/routes/delete.ts @@ -0,0 +1,61 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + params: schema.object({ + key: schema.string(), + }), +}; + +export function registerDeleteRoute(router: IRouter) { + router.delete( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + await uiSettingsClient.remove(request.params.key); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + } + ); +} diff --git a/src/core/server/ui_settings/routes/get.ts b/src/core/server/ui_settings/routes/get.ts new file mode 100644 index 00000000000000..d249369a1ace7d --- /dev/null +++ b/src/core/server/ui_settings/routes/get.ts @@ -0,0 +1,45 @@ +/* + * 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 { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; + +export function registerGetRoute(router: IRouter) { + router.get( + { path: '/api/kibana/settings', validate: false }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + throw error; + } + } + ); +} diff --git a/src/core/server/ui_settings/routes/index.ts b/src/core/server/ui_settings/routes/index.ts new file mode 100644 index 00000000000000..d70d55d7259387 --- /dev/null +++ b/src/core/server/ui_settings/routes/index.ts @@ -0,0 +1,31 @@ +/* + * 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 { IRouter } from 'src/core/server'; + +import { registerDeleteRoute } from './delete'; +import { registerGetRoute } from './get'; +import { registerSetManyRoute } from './set_many'; +import { registerSetRoute } from './set'; + +export function registerRoutes(router: IRouter) { + registerGetRoute(router); + registerDeleteRoute(router); + registerSetRoute(router); + registerSetManyRoute(router); +} diff --git a/src/core/server/ui_settings/routes/set.ts b/src/core/server/ui_settings/routes/set.ts new file mode 100644 index 00000000000000..51ad256b51335f --- /dev/null +++ b/src/core/server/ui_settings/routes/set.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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + params: schema.object({ + key: schema.string(), + }), + body: schema.object({ + value: schema.any(), + }), +}; + +export function registerSetRoute(router: IRouter) { + router.post( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + const { key } = request.params; + const { value } = request.body; + + await uiSettingsClient.set(key, value); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + } + ); +} diff --git a/src/core/server/ui_settings/routes/set_many.ts b/src/core/server/ui_settings/routes/set_many.ts new file mode 100644 index 00000000000000..3794eba004beea --- /dev/null +++ b/src/core/server/ui_settings/routes/set_many.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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + body: schema.object({ + changes: schema.object({}, { allowUnknowns: true }), + }), +}; + +export function registerSetManyRoute(router: IRouter) { + router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + const { changes } = request.body; + + await uiSettingsClient.setMany(changes); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + }); +} diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts new file mode 100644 index 00000000000000..0fa6b3702af241 --- /dev/null +++ b/src/core/server/ui_settings/types.ts @@ -0,0 +1,141 @@ +/* + * 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 { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; +/** + * Server-side client that provides access to the advanced settings stored in elasticsearch. + * The settings provide control over the behavior of the Kibana application. + * For example, a user can specify how to display numeric or date fields. + * Users can adjust the settings via Management UI. + * + * @public + */ +export interface IUiSettingsClient { + /** + * Returns registered uiSettings values {@link UiSettingsParams} + */ + getRegistered: () => Readonly>; + /** + * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. + */ + get: (key: string) => Promise; + /** + * Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. + */ + getAll: () => Promise>; + /** + * Retrieves a set of all uiSettings values set by the user. + */ + getUserProvided: () => Promise< + Record> + >; + /** + * Writes multiple uiSettings values and marks them as set by the user. + */ + setMany: (changes: Record) => Promise; + /** + * Writes uiSettings value and marks it as set by the user. + */ + set: (key: string, value: T) => Promise; + /** + * Removes uiSettings value by key. + */ + remove: (key: string) => Promise; + /** + * Removes multiple uiSettings values by keys. + */ + removeMany: (keys: string[]) => Promise; + /** + * Shows whether the uiSettings value set by the user. + */ + isOverridden: (key: string) => boolean; +} + +/** + * Describes the values explicitly set by user. + * @public + * */ +export interface UserProvidedValues { + userValue?: T; + isOverridden?: boolean; +} + +/** + * UI element type to represent the settings. + * @public + * */ +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + +/** + * UiSettings parameters defined by the plugins. + * @public + * */ +export interface UiSettingsParams { + /** title in the UI */ + name?: string; + /** default value to fall back to if a user doesn't provide any */ + value?: SavedObjectAttribute; + /** description provided to a user in UI */ + description?: string; + /** used to group the configured setting in the UI */ + category?: string[]; + /** array of permitted values for this setting */ + options?: string[]; + /** text labels for 'select' type UI element */ + optionLabels?: Record; + /** a flag indicating whether new value applying requires page reloading */ + requiresPageReload?: boolean; + /** a flag indicating that value cannot be changed */ + readonly?: boolean; + /** defines a type of UI element {@link UiSettingsType} */ + type?: UiSettingsType; +} + +/** @internal */ +export interface InternalUiSettingsServiceSetup { + /** + * Sets settings with default values for the uiSettings. + * @param settings + */ + register(settings: Record): void; + /** + * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} + * @param savedObjectsClient + */ + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + +/** @public */ +export interface UiSettingsServiceSetup { + /** + * Sets settings with default values for the uiSettings. + * @param settings + * + * @example + * setup(core: CoreSetup){ + * core.uiSettings.register([{ + * foo: { + * name: i18n.translate('my foo settings'), + * value: true, + * description: 'add some awesomeness', + * }, + * }]); + * } + */ + register(settings: Record): void; +} diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index 59c13fbebee70b..1c99637a89fed3 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -24,6 +24,7 @@ import sinon from 'sinon'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { UiSettingsClient } from './ui_settings_client'; +import { CannotOverrideError } from './ui_settings_errors'; import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; @@ -119,7 +120,12 @@ describe('ui settings', () => { await uiSettings.setMany({ foo: 'bar' }); sinon.assert.calledTwice(savedObjectsClient.update); + sinon.assert.calledOnce(createOrUpgradeSavedConfig); + sinon.assert.calledWith( + createOrUpgradeSavedConfig, + sinon.match({ handleWriteErrors: false }) + ); }); it('only tried to auto create once and throws NotFound', async () => { @@ -135,9 +141,14 @@ describe('ui settings', () => { sinon.assert.calledTwice(savedObjectsClient.update); sinon.assert.calledOnce(createOrUpgradeSavedConfig); + + sinon.assert.calledWith( + createOrUpgradeSavedConfig, + sinon.match({ handleWriteErrors: false }) + ); }); - it('throws an error if any key is overridden', async () => { + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -150,6 +161,7 @@ describe('ui settings', () => { foo: 'baz', }); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); @@ -167,7 +179,7 @@ describe('ui settings', () => { assertUpdateQuery({ one: 'value' }); }); - it('throws an error if the key is overridden', async () => { + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -177,6 +189,7 @@ describe('ui settings', () => { try { await uiSettings.set('foo', 'baz'); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); @@ -194,7 +207,7 @@ describe('ui settings', () => { assertUpdateQuery({ one: null }); }); - it('throws an error if the key is overridden', async () => { + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -204,6 +217,7 @@ describe('ui settings', () => { try { await uiSettings.remove('foo'); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); @@ -227,7 +241,7 @@ describe('ui settings', () => { assertUpdateQuery({ one: null, two: null, three: null }); }); - it('throws an error if any key is overridden', async () => { + it('throws CannotOverrideError if any key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -237,18 +251,18 @@ describe('ui settings', () => { try { await uiSettings.setMany({ baz: 'baz', foo: 'foo' }); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); }); - describe('#getDefaults()', () => { - it('returns the defaults passed to the constructor', () => { + describe('#getRegistered()', () => { + it('returns the registered settings passed to the constructor', () => { const value = chance.word(); - const { uiSettings } = setup({ defaults: { key: { value } } }); - expect(uiSettings.getDefaults()).to.eql({ - key: { value }, - }); + const defaults = { key: { value } }; + const { uiSettings } = setup({ defaults }); + expect(uiSettings.getRegistered()).to.be(defaults); }); }); @@ -284,31 +298,48 @@ describe('ui settings', () => { }); }); - it.skip('returns an empty object on NotFound responses', async () => { - const { uiSettings, savedObjectsClient } = setup(); + it('automatically creates the savedConfig if it is missing and returns empty object', async () => { + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); + savedObjectsClient.get + .onFirstCall() + .throws(savedObjectsClientErrors.createGenericNotFoundError()) + .onSecondCall() + .returns({ attributes: {} }); - const error = savedObjectsClientErrors.createGenericNotFoundError(); - savedObjectsClient.get.throws(error); + expect(await uiSettings.getUserProvided()).to.eql({}); + + sinon.assert.calledTwice(savedObjectsClient.get); + + sinon.assert.calledOnce(createOrUpgradeSavedConfig); + sinon.assert.calledWith(createOrUpgradeSavedConfig, sinon.match({ handleWriteErrors: true })); + }); - expect(await uiSettings.getUserProvided({})).to.eql({}); + it('returns result of savedConfig creation in case of notFound error', async () => { + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); + createOrUpgradeSavedConfig.resolves({ foo: 'bar ' }); + savedObjectsClient.get.throws(savedObjectsClientErrors.createGenericNotFoundError()); + + expect(await uiSettings.getUserProvided()).to.eql({ foo: { userValue: 'bar ' } }); }); it('returns an empty object on Forbidden responses', async () => { - const { uiSettings, savedObjectsClient } = setup(); + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); const error = savedObjectsClientErrors.decorateForbiddenError(new Error()); savedObjectsClient.get.throws(error); expect(await uiSettings.getUserProvided()).to.eql({}); + sinon.assert.notCalled(createOrUpgradeSavedConfig); }); it('returns an empty object on EsUnavailable responses', async () => { - const { uiSettings, savedObjectsClient } = setup(); + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); const error = savedObjectsClientErrors.decorateEsUnavailableError(new Error()); savedObjectsClient.get.throws(error); expect(await uiSettings.getUserProvided()).to.eql({}); + sinon.assert.notCalled(createOrUpgradeSavedConfig); }); it('throws Unauthorized errors', async () => { @@ -346,6 +377,7 @@ describe('ui settings', () => { const overrides = { foo: 'bar', + baz: null, }; const { uiSettings } = setup({ esDocSource, overrides }); @@ -357,57 +389,7 @@ describe('ui settings', () => { userValue: 'bar', isOverridden: true, }, - }); - }); - }); - - describe('#getRaw()', () => { - it('pulls user configuration from ES', async () => { - const esDocSource = {}; - const { uiSettings, assertGetQuery } = setup({ esDocSource }); - await uiSettings.getRaw(); - assertGetQuery(); - }); - - it(`without user configuration it's equal to the defaults`, async () => { - const esDocSource = {}; - const defaults = { key: { value: chance.word() } }; - const { uiSettings } = setup({ esDocSource, defaults }); - const result = await uiSettings.getRaw(); - expect(result).to.eql(defaults); - }); - - it(`user configuration gets merged with defaults`, async () => { - const esDocSource = { foo: 'bar' }; - const defaults = { key: { value: chance.word() } }; - const { uiSettings } = setup({ esDocSource, defaults }); - const result = await uiSettings.getRaw(); - - expect(result).to.eql({ - foo: { - userValue: 'bar', - }, - key: { - value: defaults.key.value, - }, - }); - }); - - it('includes the values for overridden keys', async () => { - const esDocSource = { foo: 'bar' }; - const defaults = { key: { value: chance.word() } }; - const overrides = { foo: true }; - const { uiSettings } = setup({ esDocSource, defaults, overrides }); - const result = await uiSettings.getRaw(); - - expect(result).to.eql({ - foo: { - userValue: true, - isOverridden: true, - }, - key: { - value: defaults.key.value, - }, + baz: { isOverridden: true }, }); }); }); @@ -545,22 +527,4 @@ describe('ui settings', () => { expect(uiSettings.isOverridden('bar')).to.be(true); }); }); - - describe('#assertUpdateAllowed()', () => { - it('returns false if no overrides defined', () => { - const { uiSettings } = setup(); - expect(uiSettings.assertUpdateAllowed('foo')).to.be(undefined); - }); - it('throws 400 Boom error when keys is overridden', () => { - const { uiSettings } = setup({ overrides: { foo: true } }); - expect(() => uiSettings.assertUpdateAllowed('foo')).to.throwError(error => { - expect(error).to.have.property( - 'message', - 'Unable to update "foo" because it is overridden' - ); - expect(error).to.have.property('isBoom', true); - expect(error.output).to.have.property('statusCode', 400); - }); - }); - }); }); diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index c495d1b4c45671..423ff2a1dfd909 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -17,12 +17,13 @@ * under the License. */ import { defaultsDeep } from 'lodash'; -import Boom from 'boom'; +import { SavedObjectsErrorHelpers } from '../saved_objects'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; import { Logger } from '../logging'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; -import { UiSettingsParams } from './ui_settings_service'; +import { IUiSettingsClient, UiSettingsParams } from './types'; +import { CannotOverrideError } from './ui_settings_errors'; export interface UiSettingsServiceOptions { type: string; @@ -49,52 +50,6 @@ type UiSettingsRawValue = UiSettingsParams & UserProvidedValue; type UserProvided = Record>; type UiSettingsRaw = Record; -/** - * Service that provides access to the UiSettings stored in elasticsearch. - * - * @public - */ -export interface IUiSettingsClient { - /** - * Returns uiSettings default values {@link UiSettingsParams} - */ - getDefaults: () => Record; - /** - * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. - */ - get: (key: string) => Promise; - /** - * Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. - */ - getAll: () => Promise>; - /** - * Retrieves a set of all uiSettings values set by the user. - */ - getUserProvided: () => Promise< - Record - >; - /** - * Writes multiple uiSettings values and marks them as set by the user. - */ - setMany: (changes: Record) => Promise; - /** - * Writes uiSettings value and marks it as set by the user. - */ - set: (key: string, value: T) => Promise; - /** - * Removes uiSettings value by key. - */ - remove: (key: string) => Promise; - /** - * Removes multiple uiSettings values by keys. - */ - removeMany: (keys: string[]) => Promise; - /** - * Shows whether the uiSettings value set by the user. - */ - isOverridden: (key: string) => boolean; -} - export class UiSettingsClient implements IUiSettingsClient { private readonly type: UiSettingsServiceOptions['type']; private readonly id: UiSettingsServiceOptions['id']; @@ -116,7 +71,7 @@ export class UiSettingsClient implements IUiSettingsClient { this.log = log; } - getDefaults() { + getRegistered() { return this.defaults; } @@ -138,19 +93,11 @@ export class UiSettingsClient implements IUiSettingsClient { ); } - // NOTE: should be a private method - async getRaw(): Promise { - const userProvided = await this.getUserProvided(); - return defaultsDeep(userProvided, this.defaults); - } - - async getUserProvided( - options: ReadOptions = {} - ): Promise> { + async getUserProvided(): Promise> { const userProvided: UserProvided = {}; // write the userValue for each key stored in the saved object that is not overridden - for (const [key, userValue] of Object.entries(await this.read(options))) { + for (const [key, userValue] of Object.entries(await this.read())) { if (userValue !== null && !this.isOverridden(key)) { userProvided[key] = { userValue, @@ -192,13 +139,17 @@ export class UiSettingsClient implements IUiSettingsClient { return this.overrides.hasOwnProperty(key); } - // NOTE: should be private method - assertUpdateAllowed(key: string) { + private assertUpdateAllowed(key: string) { if (this.isOverridden(key)) { - throw Boom.badRequest(`Unable to update "${key}" because it is overridden`); + throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`); } } + private async getRaw(): Promise { + const userProvided = await this.getUserProvided(); + return defaultsDeep(userProvided, this.defaults); + } + private async write({ changes, autoCreateOrUpgradeIfMissing = true, @@ -213,8 +164,7 @@ export class UiSettingsClient implements IUiSettingsClient { try { await this.savedObjectsClient.update(this.type, this.id, changes); } catch (error) { - const { isNotFoundError } = this.savedObjectsClient.errors; - if (!isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { + if (!SavedObjectsErrorHelpers.isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { throw error; } @@ -223,6 +173,7 @@ export class UiSettingsClient implements IUiSettingsClient { version: this.id, buildNum: this.buildNum, log: this.log, + handleWriteErrors: false, }); await this.write({ @@ -236,37 +187,17 @@ export class UiSettingsClient implements IUiSettingsClient { ignore401Errors = false, autoCreateOrUpgradeIfMissing = true, }: ReadOptions = {}): Promise> { - const { - isConflictError, - isNotFoundError, - isForbiddenError, - isNotAuthorizedError, - } = this.savedObjectsClient.errors; - try { const resp = await this.savedObjectsClient.get(this.type, this.id); return resp.attributes; } catch (error) { - if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { + if (SavedObjectsErrorHelpers.isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ savedObjectsClient: this.savedObjectsClient, version: this.id, buildNum: this.buildNum, log: this.log, - onWriteError(writeError, attributes) { - if (isConflictError(writeError)) { - // trigger `!failedUpgradeAttributes` check below, since another - // request caused the uiSettings object to be created so we can - // just re-read - return; - } - - if (isNotAuthorizedError(writeError) || isForbiddenError(writeError)) { - return attributes; - } - - throw writeError; - }, + handleWriteErrors: true, }); if (!failedUpgradeAttributes) { diff --git a/src/core/server/ui_settings/ui_settings_errors.ts b/src/core/server/ui_settings/ui_settings_errors.ts new file mode 100644 index 00000000000000..d8fc32b111e448 --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_errors.ts @@ -0,0 +1,31 @@ +/* + * 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 class CannotOverrideError extends Error { + public cause?: Error; + + constructor(message: string, cause?: Error) { + super(message); + this.cause = cause; + + // Set the prototype explicitly, see: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, CannotOverrideError.prototype); + } +} diff --git a/src/core/server/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts index 2127faf0d20299..bb21109a2f9674 100644 --- a/src/core/server/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -17,12 +17,11 @@ * under the License. */ -import { IUiSettingsClient } from './ui_settings_client'; -import { InternalUiSettingsServiceSetup } from './ui_settings_service'; +import { IUiSettingsClient, InternalUiSettingsServiceSetup } from './types'; const createClientMock = () => { const mocked: jest.Mocked = { - getDefaults: jest.fn(), + getRegistered: jest.fn(), get: jest.fn(), getAll: jest.fn(), getUserProvided: jest.fn(), @@ -38,7 +37,7 @@ const createClientMock = () => { const createSetupMock = () => { const mocked: jest.Mocked = { - setDefaults: jest.fn(), + register: jest.fn(), asScopedToClient: jest.fn(), }; @@ -48,6 +47,6 @@ const createSetupMock = () => { }; export const uiSettingsServiceMock = { - createSetup: createSetupMock, + createSetupContract: createSetupMock, createClient: createClientMock, }; diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index 832d61bdb41373..d7a085a220190b 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -23,7 +23,7 @@ import { MockUiSettingsClientConstructor } from './ui_settings_service.test.mock import { UiSettingsService } from './ui_settings_service'; import { httpServiceMock } from '../http/http_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; -import { SavedObjectsClientMock } from '../mocks'; +import { savedObjectsClientMock } from '../mocks'; import { mockCoreContext } from '../core_context.mock'; const overrides = { @@ -43,7 +43,7 @@ const coreContext = mockCoreContext.create(); coreContext.configService.atPath.mockReturnValue(new BehaviorSubject({ overrides })); const httpSetup = httpServiceMock.createSetupContract(); const setupDeps = { http: httpSetup }; -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); afterEach(() => { MockUiSettingsClientConstructor.mockClear(); @@ -52,6 +52,14 @@ afterEach(() => { describe('uiSettings', () => { describe('#setup', () => { describe('#asScopedToClient', () => { + it('passes saved object type "config" to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + const setup = await service.setup(setupDeps); + setup.asScopedToClient(savedObjectsClient); + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].type).toBe('config'); + }); + it('passes overrides to UiSettingsClient', async () => { const service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); @@ -86,7 +94,7 @@ describe('uiSettings', () => { const service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); - setup.setDefaults(defaults); + setup.register(defaults); setup.asScopedToClient(savedObjectsClient); expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); @@ -95,13 +103,13 @@ describe('uiSettings', () => { }); }); - describe('#setDefaults', () => { - it('throws if set defaults for the same key twice', async () => { + describe('#register', () => { + it('throws if registers the same key twice', async () => { const service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); - setup.setDefaults(defaults); - expect(() => setup.setDefaults(defaults)).toThrowErrorMatchingInlineSnapshot( - `"uiSettings defaults for key [foo] has been already set"` + setup.register(defaults); + expect(() => setup.register(defaults)).toThrowErrorMatchingInlineSnapshot( + `"uiSettings for the key [foo] has been already registered"` ); }); }); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 746fa514c5d4bc..a8f5663f8bd1ed 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -26,58 +26,16 @@ import { Logger } from '../logging'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; import { InternalHttpServiceSetup } from '../http'; import { UiSettingsConfigType } from './ui_settings_config'; -import { IUiSettingsClient, UiSettingsClient } from './ui_settings_client'; +import { UiSettingsClient } from './ui_settings_client'; +import { InternalUiSettingsServiceSetup, UiSettingsParams } from './types'; import { mapToObject } from '../../utils/'; +import { registerRoutes } from './routes'; + interface SetupDeps { http: InternalHttpServiceSetup; } -/** - * UI element type to represent the settings. - * @public - * */ -export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; - -/** - * UiSettings parameters defined by the plugins. - * @public - * */ -export interface UiSettingsParams { - /** title in the UI */ - name: string; - /** default value to fall back to if a user doesn't provide any */ - value: SavedObjectAttribute; - /** description provided to a user in UI */ - description: string; - /** used to group the configured setting in the UI */ - category: string[]; - /** a range of valid values */ - options?: string[]; - /** text labels for 'select' type UI element */ - optionLabels?: Record; - /** a flag indicating whether new value applying requires page reloading */ - requiresPageReload?: boolean; - /** a flag indicating that value cannot be changed */ - readonly?: boolean; - /** defines a type of UI element {@link UiSettingsType} */ - type?: UiSettingsType; -} - -/** @internal */ -export interface InternalUiSettingsServiceSetup { - /** - * Sets the parameters with default values for the uiSettings. - * @param values - */ - setDefaults(values: Record): void; - /** - * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} - * @param values - */ - asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; -} - /** @internal */ export class UiSettingsService implements CoreService { private readonly log: Logger; @@ -90,12 +48,13 @@ export class UiSettingsService implements CoreService { + registerRoutes(deps.http.createRouter('')); this.log.debug('Setting up ui settings service'); const overrides = await this.getOverrides(deps); const { version, buildNum } = this.coreContext.env.packageInfo; return { - setDefaults: this.setDefaults.bind(this), + register: this.register.bind(this), asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => { return new UiSettingsClient({ type: 'config', @@ -114,10 +73,10 @@ export class UiSettingsService implements CoreService = {}) { - Object.entries(values).forEach(([key, value]) => { + private register(settings: Record = {}) { + Object.entries(settings).forEach(([key, value]) => { if (this.uiSettingsDefaults.has(key)) { - throw new Error(`uiSettings defaults for key [${key}] has been already set`); + throw new Error(`uiSettings for the key [${key}] has been already registered`); } this.uiSettingsDefaults.set(key, value); }); diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index b6ffb57db975c1..98f0800feae79a 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -22,5 +22,6 @@ export * from './context'; export * from './deep_freeze'; export * from './get'; export * from './map_to_object'; +export * from './merge'; export * from './pick'; export * from './url'; diff --git a/src/core/utils/integration_tests/deep_freeze.test.ts b/src/core/utils/integration_tests/deep_freeze.test.ts index a9440f70ae4707..e6625542bc38ae 100644 --- a/src/core/utils/integration_tests/deep_freeze.test.ts +++ b/src/core/utils/integration_tests/deep_freeze.test.ts @@ -27,16 +27,14 @@ it( 'types return values to prevent mutations in typescript', async () => { await expect( - execa.stdout('tsc', ['--noEmit'], { + execa('tsc', ['--noEmit'], { cwd: resolve(__dirname, '__fixtures__/frozen_object_mutation'), - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(` -"Command failed: tsc --noEmit - -index.ts(28,12): error TS2540: Cannot assign to 'baz' because it is a read-only property. -index.ts(36,11): error TS2540: Cannot assign to 'bar' because it is a read-only property. -" -`); + preferLocal: true, + }).catch(err => err.stdout) + ).resolves.toMatchInlineSnapshot(` + "index.ts(28,12): error TS2540: Cannot assign to 'baz' because it is a read-only property. + index.ts(36,11): error TS2540: Cannot assign to 'bar' because it is a read-only property." + `); }, MINUTE ); diff --git a/src/core/utils/merge.test.ts b/src/core/utils/merge.test.ts new file mode 100644 index 00000000000000..aa98f510674116 --- /dev/null +++ b/src/core/utils/merge.test.ts @@ -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 { merge } from './merge'; + +describe('merge', () => { + test('empty objects', () => expect(merge({}, {})).toEqual({})); + + test('basic', () => { + expect(merge({}, { a: 1 })).toEqual({ a: 1 }); + expect(merge({ a: 0 }, {})).toEqual({ a: 0 }); + expect(merge({ a: 0 }, { a: 1 })).toEqual({ a: 1 }); + }); + + test('undefined', () => { + expect(merge({ a: undefined }, { a: 1 })).toEqual({ a: 1 }); + expect(merge({ a: 0 }, { a: undefined })).toEqual({ a: 0 }); + expect(merge({ a: undefined }, { a: undefined })).toEqual({}); + expect(merge({ a: void 0 }, { a: void 0 })).toEqual({}); + }); + + test('null', () => { + expect(merge({ a: null }, { a: 1 })).toEqual({ a: 1 }); + expect(merge({ a: 0 }, { a: null })).toEqual({ a: null }); + expect(merge({ a: null }, { a: null })).toEqual({ a: null }); + }); + + test('arrays', () => { + expect(merge({ b: [0] }, { b: [2] })).toEqual({ b: [2] }); + expect(merge({ b: [0, 1] }, { b: [2] })).toEqual({ b: [2] }); + expect(merge({ b: [0] }, { b: [2, 3] })).toEqual({ b: [2, 3] }); + expect(merge({ b: [] }, { b: [2] })).toEqual({ b: [2] }); + expect(merge({ b: [0] }, { b: [] })).toEqual({ b: [] }); + }); + + test('nested objects', () => { + expect(merge({ top: { a: 0, b: 0 } }, { top: { a: 1, c: 1 } })).toEqual({ + top: { a: 1, b: 0, c: 1 }, + }); + expect(merge({ top: { a: 0, b: 0 } }, { top: [0, 1] })).toEqual({ top: [0, 1] }); + }); + + test('multiple objects', () => { + expect(merge({}, { a: 1 }, { a: 2 })).toEqual({ a: 2 }); + expect(merge({ a: 0 }, {}, {})).toEqual({ a: 0 }); + expect(merge({ a: 0 }, { a: 1 }, {})).toEqual({ a: 1 }); + }); +}); diff --git a/src/core/utils/merge.ts b/src/core/utils/merge.ts new file mode 100644 index 00000000000000..aead3f35ba841b --- /dev/null +++ b/src/core/utils/merge.ts @@ -0,0 +1,85 @@ +/* + * 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. + */ + +/** + * Deeply merges two objects, omitting undefined values, and not deeply merging Arrays. + * + * @remarks + * Should behave identically to lodash.merge, however it will not merge Array values like lodash does. + * Any properties with `undefined` values on both objects will be ommitted from the returned object. + */ +export function merge, TSource1 extends Record>( + baseObj: TBase, + source1: TSource1 +): TBase & TSource1; +export function merge< + TBase extends Record, + TSource1 extends Record, + TSource2 extends Record +>(baseObj: TBase, overrideObj: TSource1, overrideObj2: TSource2): TBase & TSource1 & TSource2; +export function merge< + TBase extends Record, + TSource1 extends Record, + TSource2 extends Record, + TSource3 extends Record +>( + baseObj: TBase, + overrideObj: TSource1, + overrideObj2: TSource2 +): TBase & TSource1 & TSource2 & TSource3; +export function merge>( + baseObj: Record, + ...sources: Array> +): TReturn { + const firstSource = sources[0]; + if (firstSource === undefined) { + return baseObj as TReturn; + } + + return sources + .slice(1) + .reduce( + (merged, nextSource) => mergeObjects(merged, nextSource), + mergeObjects(baseObj, firstSource) + ) as TReturn; +} + +const isMergable = (obj: any) => typeof obj === 'object' && obj !== null && !Array.isArray(obj); + +const mergeObjects = , U extends Record>( + baseObj: T, + overrideObj: U +): T & U => + [...new Set([...Object.keys(baseObj), ...Object.keys(overrideObj)])].reduce( + (merged, key) => { + const baseVal = baseObj[key]; + const overrideVal = overrideObj[key]; + + if (isMergable(baseVal) && isMergable(overrideVal)) { + merged[key] = mergeObjects(baseVal, overrideVal); + } else if (overrideVal !== undefined) { + merged[key] = overrideVal; + } else if (baseVal !== undefined) { + merged[key] = baseVal; + } + + return merged; + }, + {} as any + ); diff --git a/src/dev/build/build_distributables.js b/src/dev/build/build_distributables.js index e3fcfd9536c47c..eeffc2380f38b3 100644 --- a/src/dev/build/build_distributables.js +++ b/src/dev/build/build_distributables.js @@ -45,7 +45,6 @@ import { ExtractNodeBuildsTask, InstallDependenciesTask, OptimizeBuildTask, - PatchNativeModulesTask, RemovePackageJsonDepsTask, RemoveWorkspacesTask, TranspileBabelTask, @@ -131,7 +130,6 @@ export async function buildDistributables(options) { * directories and perform platform-specific steps */ await run(CreateArchivesSourcesTask); - await run(PatchNativeModulesTask); await run(CleanExtraBinScriptsTask); await run(CleanExtraBrowsersTask); await run(CleanNodeBuildsTask); diff --git a/src/dev/build/lib/exec.js b/src/dev/build/lib/exec.js index 57dced92ca6255..82762f3bd03a9e 100644 --- a/src/dev/build/lib/exec.js +++ b/src/dev/build/lib/exec.js @@ -36,6 +36,7 @@ export async function exec(log, cmd, args, options = {}) { stdio: ['ignore', 'pipe', 'pipe'], cwd, env, + preferLocal: true, }); await watchStdioForLine(proc, line => log[level](line), exitAfter); diff --git a/src/dev/build/lib/scan_delete.ts b/src/dev/build/lib/scan_delete.ts index f1b2a02c11a435..cb4e64ce1b5f98 100644 --- a/src/dev/build/lib/scan_delete.ts +++ b/src/dev/build/lib/scan_delete.ts @@ -28,7 +28,7 @@ import { count, map, mergeAll, mergeMap } from 'rxjs/operators'; import { assertAbsolute } from './fs'; const getStat$ = Rx.bindNodeCallback(Fs.stat); -const getReadDir$ = Rx.bindNodeCallback(Fs.readdir); +const getReadDir$ = Rx.bindNodeCallback(Fs.readdir); interface Options { directory: string; diff --git a/src/dev/build/lib/version_info.js b/src/dev/build/lib/version_info.js index b0f51eaaa1d79a..8225000c13f07b 100644 --- a/src/dev/build/lib/version_info.js +++ b/src/dev/build/lib/version_info.js @@ -28,7 +28,9 @@ async function getBuildNumber() { return log.stdout.split('\n').length; } - const wc = await execa.shell('git log --format="%h" | wc -l'); + const wc = await execa.command('git log --format="%h" | wc -l', { + shell: true + }); return parseFloat(wc.stdout.trim()); } @@ -39,7 +41,7 @@ export async function getVersionInfo({ isRelease, versionQualifier, pkg }) { ); return { - buildSha: await execa.stdout('git', ['rev-parse', 'HEAD']), + buildSha: (await execa('git', ['rev-parse', 'HEAD'])).stdout, buildVersion, buildNumber: await getBuildNumber(), }; diff --git a/src/dev/build/tasks/index.js b/src/dev/build/tasks/index.js index 35c07c91873172..014cdc7ad5ea78 100644 --- a/src/dev/build/tasks/index.js +++ b/src/dev/build/tasks/index.js @@ -36,5 +36,4 @@ export * from './transpile_babel_task'; export * from './transpile_scss_task'; export * from './verify_env_task'; export * from './write_sha_sums_task'; -export * from './patch_native_modules_task'; export * from './path_length_task'; diff --git a/src/dev/build/tasks/nodejs/__tests__/download.js b/src/dev/build/tasks/nodejs/__tests__/download.js index 551773a6f63253..c76ff15b892893 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download.js +++ b/src/dev/build/tasks/nodejs/__tests__/download.js @@ -196,7 +196,7 @@ describe('src/dev/build/tasks/nodejs/download', () => { } catch (error) { expect(error) .to.have.property('message') - .contain('Unexpected status code 500'); + .contain('Request failed with status code 500'); expect(reqCount).to.be(6); } }); diff --git a/src/dev/build/tasks/nodejs/download.js b/src/dev/build/tasks/nodejs/download.js index 48313c0911c247..0a030aced0d424 100644 --- a/src/dev/build/tasks/nodejs/download.js +++ b/src/dev/build/tasks/nodejs/download.js @@ -22,7 +22,7 @@ import { dirname } from 'path'; import chalk from 'chalk'; import { createHash } from 'crypto'; -import wreck from '@hapi/wreck'; +import Axios from 'axios'; import { mkdirp } from '../../lib'; @@ -51,21 +51,24 @@ export async function download(options) { try { log.debug(`Attempting download of ${url}`, chalk.dim(sha256)); - const response = await wreck.request('GET', url); + const response = await Axios.request({ + url: url, + responseType: 'stream' + }); - if (response.statusCode !== 200) { - throw new Error(`Unexpected status code ${response.statusCode} when downloading ${url}`); + if (response.status !== 200) { + throw new Error(`Unexpected status code ${response.status} when downloading ${url}`); } const hash = createHash('sha256'); await new Promise((resolve, reject) => { - response.on('data', chunk => { + response.data.on('data', chunk => { hash.update(chunk); writeSync(fileHandle, chunk); }); - response.on('error', reject); - response.on('end', resolve); + response.data.on('error', reject); + response.data.on('end', resolve); }); const downloadedSha256 = hash.digest('hex'); diff --git a/src/dev/build/tasks/nodejs/node_download_info.js b/src/dev/build/tasks/nodejs/node_download_info.js index 7c4fd5fde7bedc..33ffd042d85a36 100644 --- a/src/dev/build/tasks/nodejs/node_download_info.js +++ b/src/dev/build/tasks/nodejs/node_download_info.js @@ -27,7 +27,7 @@ export function getNodeDownloadInfo(config, platform) { ? 'win-x64/node.exe' : `node-v${version}-${arch}.tar.gz`; - const url = `https://nodejs.org/dist/v${version}/${downloadName}`; + const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${version}/${downloadName}`; const downloadPath = config.resolveFromRepo('.node_binaries', version, basename(downloadName)); const extractDir = config.resolveFromRepo('.node_binaries', version, arch); diff --git a/src/dev/build/tasks/nodejs/node_shasums.test.ts b/src/dev/build/tasks/nodejs/node_shasums.test.ts index ee91d2a370fb12..08ac823c7ebf03 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.test.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.test.ts @@ -60,7 +60,9 @@ c4edece2c0aa68e816c4e067f397eb12e9d0c81bb37b3d349dbaf47cf246b0b7 win-x86/node.l jest.mock('axios', () => ({ async get(url: string) { - expect(url).toBe('https://nodejs.org/dist/v8.9.4/SHASUMS256.txt'); + expect(url).toBe( + 'https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v8.9.4/SHASUMS256.txt' + ); return { status: 200, data: mockResponse, diff --git a/src/dev/build/tasks/nodejs/node_shasums.ts b/src/dev/build/tasks/nodejs/node_shasums.ts index 1b8d01a9b1d94c..e0926aa3e49e4b 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.ts @@ -20,7 +20,7 @@ import axios from 'axios'; export async function getNodeShasums(nodeVersion: string) { - const url = `https://nodejs.org/dist/v${nodeVersion}/SHASUMS256.txt`; + const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${nodeVersion}/SHASUMS256.txt`; const { status, data } = await axios.get(url); diff --git a/src/dev/build/tasks/patch_native_modules_task.js b/src/dev/build/tasks/patch_native_modules_task.js deleted file mode 100644 index 16290aca04e736..00000000000000 --- a/src/dev/build/tasks/patch_native_modules_task.js +++ /dev/null @@ -1,47 +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 install from '@elastic/simple-git/scripts/install'; -import { deleteAll } from '../lib'; -import path from 'path'; - -async function patchGit(config, log, build, platform) { - const downloadPath = build.resolvePathForPlatform(platform, '.git_binaries', 'git.tar.gz'); - const destination = build.resolvePathForPlatform( - platform, - 'node_modules/@elastic/simple-git/native/git' - ); - log.debug('Replacing git binaries from ' + downloadPath + ' to ' + destination); - const p = platform.isWindows() ? 'win32' : platform.getName(); - await deleteAll([destination]); - await install(p, downloadPath, destination); - await deleteAll([path.dirname(downloadPath)], log); -} - -export const PatchNativeModulesTask = { - description: 'Patching platform-specific native modules directories', - async run(config, log, build) { - await Promise.all( - config.getTargetPlatforms().map(async platform => { - if (!build.isOss()) { - await patchGit(config, log, build, platform); - } - }) - ); - }, -}; diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index b9fe8fe77d1221..805b77365e624f 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -2,6 +2,10 @@ set -e +if [[ "$CI_ENV_SETUP" ]]; then + return 0 +fi + installNode=$1 dir="$(pwd)" @@ -53,10 +57,10 @@ nodeDir="$cacheDir/node/$nodeVersion" if [[ "$OS" == "win" ]]; then nodeBin="$HOME/node" - nodeUrl="https://nodejs.org/dist/v$nodeVersion/node-v$nodeVersion-win-x64.zip" + nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-win-x64.zip" else nodeBin="$nodeDir/bin" - nodeUrl="https://nodejs.org/dist/v$nodeVersion/node-v$nodeVersion-linux-x64.tar.gz" + nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-linux-x64.tar.gz" fi if [[ "$installNode" == "true" ]]; then @@ -75,11 +79,11 @@ if [[ "$installNode" == "true" ]]; then mkdir -p "$nodeDir" if [[ "$OS" == "win" ]]; then nodePkg="$nodeDir/${nodeUrl##*/}" - curl --silent -o "$nodePkg" "$nodeUrl" + curl --silent -L -o "$nodePkg" "$nodeUrl" unzip -qo "$nodePkg" -d "$nodeDir" mv "${nodePkg%.*}" "$nodeBin" else - curl --silent "$nodeUrl" | tar -xz -C "$nodeDir" --strip-components=1 + curl --silent -L "$nodeUrl" | tar -xz -C "$nodeDir" --strip-components=1 fi fi fi @@ -152,3 +156,5 @@ if [[ -d "$ES_DIR" && -f "$ES_JAVA_PROP_PATH" ]]; then echo "Setting JAVA_HOME=$HOME/.java/$ES_BUILD_JAVA" export JAVA_HOME=$HOME/.java/$ES_BUILD_JAVA fi + +export CI_ENV_SETUP=true diff --git a/src/dev/i18n/extractors/code.js b/src/dev/i18n/extractors/code.js index 58ca059be54917..fa0d834824e97c 100644 --- a/src/dev/i18n/extractors/code.js +++ b/src/dev/i18n/extractors/code.js @@ -67,7 +67,7 @@ export function* extractCodeMessages(buffer, reporter) { try { ast = parse(buffer.toString(), { sourceType: 'module', - plugins: ['jsx', 'typescript', 'objectRestSpread', 'classProperties', 'asyncGenerators'], + plugins: ['jsx', 'typescript', 'objectRestSpread', 'classProperties', 'asyncGenerators', 'dynamicImport'], }); } catch (error) { if (error instanceof SyntaxError) { diff --git a/src/dev/jest/integration_tests/junit_reporter.test.js b/src/dev/jest/integration_tests/junit_reporter.test.js index c26c8cfad80258..ed5d73cd87c40f 100644 --- a/src/dev/jest/integration_tests/junit_reporter.test.js +++ b/src/dev/jest/integration_tests/junit_reporter.test.js @@ -52,7 +52,7 @@ it( } ); - expect(result.code).toBe(1); + expect(result.exitCode).toBe(1); await expect(parseXml(readFileSync(XML_PATH, 'utf8'))).resolves.toEqual({ testsuites: { $: { diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 727c51f704431b..fbd16d95ded1c9 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -106,8 +106,5 @@ export const LICENSE_OVERRIDES = { // TODO can be removed once we upgrade the use of walk dependency past or equal to v2.3.14 'walk@2.3.9': ['MIT'], - // TODO remove this once we upgrade past or equal to v1.0.2 - 'babel-plugin-mock-imports@1.0.1': ['MIT'], - '@elastic/node-ctags@1.0.2': ['Nuclide software'], }; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 9829de2fd39205..edd818e1b42de8 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -43,8 +43,9 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/docs/**/*', 'src/legacy/ui/public/assets/fonts/**/*', 'packages/kbn-utility-types/test-d/**/*', - 'Jenkinsfile', + '**/Jenkinsfile*', 'Dockerfile*', + 'vars/*', // Files in this directory must match a pre-determined name in some cases. 'x-pack/legacy/plugins/canvas/.storybook/*', diff --git a/src/dev/prs/run_update_prs_cli.ts b/src/dev/prs/run_update_prs_cli.ts index 6d99ac5fa45f30..bb7f50758a28c8 100644 --- a/src/dev/prs/run_update_prs_cli.ts +++ b/src/dev/prs/run_update_prs_cli.ts @@ -86,7 +86,7 @@ run( // attempt to init upstream remote await execInDir('git', ['remote', 'add', 'upstream', UPSTREAM_URL]); } catch (error) { - if (error.code !== 128) { + if (error.exitCode !== 128) { throw error; } diff --git a/src/dev/run_check_core_api_changes.ts b/src/dev/run_check_core_api_changes.ts index d2c75c86ce7442..ccf92cc432d2b0 100644 --- a/src/dev/run_check_core_api_changes.ts +++ b/src/dev/run_check_core_api_changes.ts @@ -76,12 +76,16 @@ const apiExtractorConfig = (folder: string): ExtractorConfig => { }; const runBuildTypes = async () => { - await execa.shell('yarn run build:types'); + await execa('yarn', ['run', 'build:types']); }; const runApiDocumenter = async (folder: string) => { - await execa.shell( - `api-documenter markdown -i ./build/${folder} -o ./docs/development/core/${folder}` + await execa( + 'api-documenter', + ['markdown', '-i', `./build/${folder}`, '-o', `./docs/development/core/${folder}`], + { + preferLocal: true, + } ); }; diff --git a/src/dev/typescript/exec_in_projects.ts b/src/dev/typescript/exec_in_projects.ts index 8895e964c78170..a34f2bdd28670e 100644 --- a/src/dev/typescript/exec_in_projects.ts +++ b/src/dev/typescript/exec_in_projects.ts @@ -45,6 +45,7 @@ export function execInProjects( cwd: process.cwd(), env: chalk.enabled ? { FORCE_COLOR: 'true' } : {}, stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true, }).catch(error => { throw new ProjectFailure(project, error); }), diff --git a/src/dev/typescript/run_check_ts_projects_cli.ts b/src/dev/typescript/run_check_ts_projects_cli.ts index f42a4e27593709..85f3d473dce6bc 100644 --- a/src/dev/typescript/run_check_ts_projects_cli.ts +++ b/src/dev/typescript/run_check_ts_projects_cli.ts @@ -30,7 +30,7 @@ import { PROJECTS } from './projects'; export async function runCheckTsProjectsCli() { run( async ({ log }) => { - const files = await execa.stdout('git', ['ls-tree', '--name-only', '-r', 'HEAD'], { + const { stdout: files } = await execa('git', ['ls-tree', '--name-only', '-r', 'HEAD'], { cwd: REPO_ROOT, }); diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index bf6e8711fa81e9..3deebcc0c18f9a 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -88,7 +88,7 @@ export function runTypeCheckCli() { } execInProjects(log, projects, process.execPath, project => [ - ...(project.name === 'x-pack' ? ['--max-old-space-size=2048'] : []), + ...(project.name === 'x-pack' ? ['--max-old-space-size=4096'] : []), require.resolve('typescript/bin/tsc'), ...['--project', project.tsConfigPath], ...tscArgs, diff --git a/src/fixtures/logstash_fields.js b/src/fixtures/logstash_fields.js index 8299604d3a28c5..ab96b69851b713 100644 --- a/src/fixtures/logstash_fields.js +++ b/src/fixtures/logstash_fields.js @@ -19,13 +19,13 @@ import { castEsToKbnFieldTypeName } from '../plugins/data/common'; // eslint-disable-next-line max-len -import { shouldReadFieldFromDocValues } from '../legacy/server/index_patterns/service/lib/field_capabilities/should_read_field_from_doc_values'; +import { shouldReadFieldFromDocValues } from '../plugins/data/server'; function stubbedLogstashFields() { return [ // |aggregatable // | |searchable - // name esType | | |metadata | parent | subType + // name esType | | |metadata | subType ['bytes', 'long', true, true, { count: 10 } ], ['ssl', 'boolean', true, true, { count: 20 } ], ['@timestamp', 'date', true, true, { count: 30 } ], @@ -40,9 +40,9 @@ function stubbedLogstashFields() { ['hashed', 'murmur3', false, true ], ['geo.coordinates', 'geo_point', true, true ], ['extension', 'text', true, true], - ['extension.keyword', 'keyword', true, true, {}, 'extension', 'multi' ], + ['extension.keyword', 'keyword', true, true, {}, { multi: { parent: 'extension' } } ], ['machine.os', 'text', true, true ], - ['machine.os.raw', 'keyword', true, true, {}, 'machine.os', 'multi' ], + ['machine.os.raw', 'keyword', true, true, {}, { multi: { parent: 'machine.os' } } ], ['geo.src', 'keyword', true, true ], ['_id', '_id', true, true ], ['_type', '_type', true, true ], @@ -61,7 +61,6 @@ function stubbedLogstashFields() { aggregatable, searchable, metadata = {}, - parent = undefined, subType = undefined, ] = row; @@ -87,7 +86,6 @@ function stubbedLogstashFields() { script, lang, scripted, - parent, subType, }; }); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts index 1be571f2739ac1..d1605c1aefa566 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { throttle } from 'lodash'; interface Actions { input: any; // TODO: Wrap this in an editor interface @@ -24,6 +25,10 @@ interface Actions { } export function registerCommands({ input, sendCurrentRequestToES, openDocumentation }: Actions) { + const throttledAutoIndent = throttle(() => input.autoIndent(), 500, { + leading: true, + trailing: true, + }); input.commands.addCommand({ name: 'send to elasticsearch', bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' }, @@ -40,7 +45,7 @@ export function registerCommands({ input, sendCurrentRequestToES, openDocumentat name: 'auto indent request', bindKey: { win: 'Ctrl-I', mac: 'Command-I' }, exec: () => { - input.autoIndent(); + throttledAutoIndent(); }, }); input.commands.addCommand({ diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx index 82256cf7398820..4e5afbdb5821e9 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx @@ -19,7 +19,8 @@ import React, { useCallback, useState } from 'react'; import { debounce } from 'lodash'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { EditorOutput, Editor, ConsoleHistory } from '../editor'; import { Settings } from '../settings'; @@ -77,6 +78,13 @@ export function Main() { responsive={false} > + +

+ {i18n.translate('console.pageHeading', { + defaultMessage: 'Console', + })} +

+
setShowHistory(!showingHistory), diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts new file mode 100644 index 00000000000000..0dc1bcc96ddee2 --- /dev/null +++ b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { Range as AceRange } from 'brace'; +import { LegacyEditor } from './legacy_editor'; + +describe('Legacy Editor', () => { + const aceMock: any = { + getValue() { + return 'ok'; + }, + + getCursorPosition() { + return { + row: 1, + column: 1, + }; + }, + + getSession() { + return { + replace(range: AceRange, value: string) {}, + getLine(n: number) { + return 'line'; + }, + doc: { + getTextRange(r: any) { + return ''; + }, + }, + getState(n: number) { + return n; + }, + }; + }, + }; + + // This is to ensure that we are correctly importing Ace's Range component + it('smoke tests for updates to ranges', () => { + const legacyEditor = new LegacyEditor(aceMock); + legacyEditor.getValueInRange({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 2, column: 2 }, + }); + legacyEditor.replace( + { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 2, column: 2 }, + }, + 'test!' + ); + }); +}); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts index 1b083adcfea76a..f8c3f425a10320 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts +++ b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts @@ -17,10 +17,13 @@ * under the License. */ -import { Editor as IAceEditor, Range as AceRange } from 'brace'; +import ace from 'brace'; +import { Editor as IAceEditor } from 'brace'; import { CoreEditor, Position, Range, Token, TokensProvider } from '../../types'; import { AceTokensProvider } from '../../lib/ace_token_provider'; +const _AceRange = ace.acequire('ace/range').Range; + export class LegacyEditor implements CoreEditor { constructor(private readonly editor: IAceEditor) {} @@ -31,7 +34,7 @@ export class LegacyEditor implements CoreEditor { getValueInRange({ start, end }: Range): string { const session = this.editor.getSession(); - const aceRange = new AceRange( + const aceRange = new _AceRange( start.lineNumber - 1, start.column - 1, end.lineNumber - 1, @@ -90,7 +93,7 @@ export class LegacyEditor implements CoreEditor { } replace({ start, end }: Range, value: string): void { - const aceRange = new AceRange( + const aceRange = new _AceRange( start.lineNumber - 1, start.column - 1, end.lineNumber - 1, diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index af9803c97749e8..1a2d312823f6f2 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -30,7 +30,6 @@ import uiRoutes from 'ui/routes'; import { DOC_LINK_VERSION } from 'ui/documentation_links'; import { I18nContext } from 'ui/i18n'; import { ResizeChecker } from 'ui/resize_checker'; -import 'ui/autoload/styles'; import 'ui/capabilities/route_setup'; /* eslint-enable @kbn/eslint/no-restricted-paths */ diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts index 41869f41c222f8..47edf42f0eec55 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts +++ b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts @@ -317,16 +317,16 @@ export default function({ coreEditor: editor, parser, execCommand, - getCursor, - isCompleteActive, + getCursorPosition, + isCompleterActive, addChangeListener, removeChangeListener, }: { coreEditor: LegacyEditor; parser: any; execCommand: (cmd: string) => void; - getCursor: () => any; - isCompleteActive: () => boolean; + getCursorPosition: () => Position | null; + isCompleterActive: () => boolean; addChangeListener: (fn: any) => void; removeChangeListener: (fn: any) => void; }) { @@ -969,11 +969,10 @@ export default function({ 100); function editorChangeListener() { - const cursor = getCursor(); - if (isCompleteActive()) { - return; + const position = getCursorPosition(); + if (position && !isCompleterActive()) { + evaluateCurrentTokenAfterAChange(position); } - evaluateCurrentTokenAfterAChange(cursor); } function getCompletions( diff --git a/src/legacy/core_plugins/console/public/quarantined/src/input.ts b/src/legacy/core_plugins/console/public/quarantined/src/input.ts index a5e38e7a06e0e2..eb93f8e165cb57 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/input.ts +++ b/src/legacy/core_plugins/console/public/quarantined/src/input.ts @@ -24,6 +24,7 @@ import { LegacyEditor } from '../../../np_ready/public/application/models'; // @ts-ignore import SenseEditor from './sense_editor/editor'; +import { Position } from '../../../np_ready/public/types'; let input: any; export function initializeEditor($el: JQuery, $actionsEl: JQuery) { @@ -35,8 +36,18 @@ export function initializeEditor($el: JQuery, $actionsEl: JQuery input.execCommand(cmd), - getCursor: () => input.selection.lead, - isCompleteActive: () => input.__ace.completer && input.__ace.completer.activated, + getCursorPosition: (): Position | null => { + if (input.selection && input.selection.lead) { + return { + lineNumber: input.selection.lead.row + 1, + column: input.selection.lead.column + 1, + }; + } + return null; + }, + isCompleterActive: () => { + return Boolean(input.__ace.completer && input.__ace.completer.activated); + }, addChangeListener: (fn: any) => input.on('changeSelection', fn), removeChangeListener: (fn: any) => input.off('changeSelection', fn), }; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/index.ts index 74682722e9b893..714203de203851 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/index.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/index.ts @@ -23,7 +23,6 @@ import { resolve } from 'path'; export default function(kibana: any) { return new kibana.Plugin({ uiExports: { - hacks: ['plugins/dashboard_embeddable_container/initialize'], styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, }); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/index.scss b/src/legacy/core_plugins/dashboard_embeddable_container/public/index.scss index 88e570d1378938..548e85746f866d 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/index.scss +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/index.scss @@ -1,6 +1,3 @@ @import 'src/legacy/ui/public/styles/styling_constants'; -@import 'src/legacy/core_plugins/embeddable_api/public/variables'; -@import './np_ready/public/lib/embeddable/grid/index'; -@import './np_ready/public/lib/embeddable/panel/index'; -@import './np_ready/public/lib/embeddable/viewport/index'; +@import '../../../../plugins/dashboard_embeddable_container/public/index'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/initialize.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/initialize.ts index a4bc3cf17026c3..9880b336e76e5c 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/initialize.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/initialize.ts @@ -16,5 +16,3 @@ * specific language governing permissions and limitations * under the License. */ - -import './np_ready/public/legacy'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/kibana.json b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/kibana.json deleted file mode 100644 index 417168ba61dd94..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/kibana.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "dashboard_embeddable_container", - "version": "kibana", - "requiredPlugins": [ - "embeddable", - "inspector", - "ui_actions" - ], - "server": false, - "ui": true -} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts index cbbbd41827853a..d8c0de2bce3f4a 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts @@ -17,13 +17,4 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; -import { DashboardEmbeddableContainerPublicPlugin } from './plugin'; - -export * from './lib'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new DashboardEmbeddableContainerPublicPlugin(initializerContext); -} - -export { DashboardEmbeddableContainerPublicPlugin as Plugin }; +export * from '../../../../../../plugins/dashboard_embeddable_container/public'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/legacy.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/legacy.ts index ccb04d8c2e0276..9880b336e76e5c 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/legacy.ts @@ -16,32 +16,3 @@ * specific language governing permissions and limitations * under the License. */ - -/* eslint-disable @kbn/eslint/no-restricted-paths */ -import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import { ExitFullScreenButton } from 'ui/exit_full_screen'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ - -import { plugin } from '.'; -import { - setup as embeddableSetup, - start as embeddableStart, -} from '../../../../embeddable_api/public/np_ready/public/legacy'; - -const pluginInstance = plugin({} as any); - -export const setup = pluginInstance.setup(npSetup.core, { - embeddable: embeddableSetup, - uiActions: npSetup.plugins.uiActions, -}); - -export const start = pluginInstance.start(npStart.core, { - embeddable: embeddableStart, - inspector: npStart.plugins.inspector, - __LEGACY: { - SavedObjectFinder, - ExitFullScreenButton, - }, - uiActions: npStart.plugins.uiActions, -}); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts deleted file mode 100644 index b0707610cf21b4..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts +++ /dev/null @@ -1,20 +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. - */ - -export { ExpandPanelAction, EXPAND_PANEL_ACTION } from './expand_panel_action'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.test.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.test.tsx deleted file mode 100644 index 06bc696b95193d..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.test.tsx +++ /dev/null @@ -1,141 +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. - */ - -// @ts-ignore -import { findTestSubject } from '@elastic/eui/lib/test'; -import { nextTick } from 'test_utils/enzyme_helpers'; -import { isErrorEmbeddable, ViewMode, EmbeddableFactory } from '../embeddable_api'; -import { DashboardContainer, DashboardContainerOptions } from './dashboard_container'; -import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; -import { - CONTACT_CARD_EMBEDDABLE, - ContactCardEmbeddableFactory, - ContactCardEmbeddableInput, - ContactCardEmbeddable, - ContactCardEmbeddableOutput, -} from '../../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; - -const options: DashboardContainerOptions = { - application: {} as any, - embeddable: { - getTriggerCompatibleActions: (() => []) as any, - getEmbeddableFactories: (() => []) as any, - getEmbeddableFactory: undefined as any, - } as any, - notifications: {} as any, - overlays: {} as any, - inspector: {} as any, - SavedObjectFinder: () => null, - ExitFullScreenButton: () => null, - uiActions: {} as any, -}; - -beforeEach(() => { - const embeddableFactories = new Map(); - embeddableFactories.set( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) - ); - options.embeddable.getEmbeddableFactory = (id: string) => embeddableFactories.get(id) as any; -}); - -test('DashboardContainer initializes embeddables', async done => { - const initialInput = getSampleDashboardInput({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), - }, - }); - const container = new DashboardContainer(initialInput, options); - - const subscription = container.getOutput$().subscribe(output => { - if (container.getOutput().embeddableLoaded['123']) { - const embeddable = container.getChild('123'); - expect(embeddable).toBeDefined(); - expect(embeddable.id).toBe('123'); - done(); - } - }); - - if (container.getOutput().embeddableLoaded['123']) { - const embeddable = container.getChild('123'); - expect(embeddable).toBeDefined(); - expect(embeddable.id).toBe('123'); - subscription.unsubscribe(); - done(); - } -}); - -test('DashboardContainer.addNewEmbeddable', async () => { - const container = new DashboardContainer(getSampleDashboardInput(), options); - const embeddable = await container.addNewEmbeddable( - CONTACT_CARD_EMBEDDABLE, - { - firstName: 'Kibana', - } - ); - expect(embeddable).toBeDefined(); - - if (!isErrorEmbeddable(embeddable)) { - expect(embeddable.getInput().firstName).toBe('Kibana'); - } else { - expect(false).toBe(true); - } - - const embeddableInContainer = container.getChild(embeddable.id); - expect(embeddableInContainer).toBeDefined(); - expect(embeddableInContainer.id).toBe(embeddable.id); -}); - -test('Container view mode change propagates to existing children', async () => { - const initialInput = getSampleDashboardInput({ - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Sam', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), - }, - }); - const container = new DashboardContainer(initialInput, options); - await nextTick(); - - const embeddable = await container.getChild('123'); - expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); - container.updateInput({ viewMode: ViewMode.EDIT }); - expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); -}); - -test('Container view mode change propagates to new children', async () => { - const container = new DashboardContainer(getSampleDashboardInput(), options); - const embeddable = await container.addNewEmbeddable< - ContactCardEmbeddableInput, - ContactCardEmbeddableOutput, - ContactCardEmbeddable - >(CONTACT_CARD_EMBEDDABLE, { - firstName: 'Bob', - }); - - expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); - - container.updateInput({ viewMode: ViewMode.EDIT }); - - expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); -}); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container_factory.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container_factory.ts deleted file mode 100644 index 29ce9bcb0da2e8..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container_factory.ts +++ /dev/null @@ -1,83 +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 { SavedObjectAttributes } from '../../../../../../../../core/server'; -import { SavedObjectMetaData } from '../types'; -import { ContainerOutput, EmbeddableFactory, ErrorEmbeddable, Container } from '../embeddable_api'; -import { - DashboardContainer, - DashboardContainerInput, - DashboardContainerOptions, -} from './dashboard_container'; -import { DashboardCapabilities } from '../types'; - -export const DASHBOARD_CONTAINER_TYPE = 'dashboard'; - -export interface DashboardOptions extends DashboardContainerOptions { - savedObjectMetaData?: SavedObjectMetaData; -} - -export class DashboardContainerFactory extends EmbeddableFactory< - DashboardContainerInput, - ContainerOutput -> { - public readonly isContainerType = true; - public readonly type = DASHBOARD_CONTAINER_TYPE; - - private readonly allowEditing: boolean; - - constructor(private readonly options: DashboardOptions) { - super({ savedObjectMetaData: options.savedObjectMetaData }); - - const capabilities = (options.application.capabilities - .dashboard as unknown) as DashboardCapabilities; - - if (!capabilities || typeof capabilities !== 'object') { - throw new TypeError('Dashboard capabilities not found.'); - } - - this.allowEditing = !!capabilities.createNew && !!capabilities.showWriteControls; - } - - public isEditable() { - return this.allowEditing; - } - - public getDisplayName() { - return i18n.translate('dashboardEmbeddableContainer.factory.displayName', { - defaultMessage: 'dashboard', - }); - } - - public getDefaultInput(): Partial { - return { - panels: {}, - isFullScreenMode: false, - useMargins: true, - }; - } - - public async create( - initialInput: DashboardContainerInput, - parent?: Container - ): Promise { - return new DashboardContainer(initialInput, this.options, parent); - } -} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/index.ts deleted file mode 100644 index 2a6a56e3300bcb..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/index.ts +++ /dev/null @@ -1,30 +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. - */ - -export { DASHBOARD_CONTAINER_TYPE, DashboardContainerFactory } from './dashboard_container_factory'; -export { DashboardContainer, DashboardContainerInput } from './dashboard_container'; -export { createPanelState } from './panel'; - -export { DashboardPanelState, GridData } from './types'; - -export { - DASHBOARD_GRID_COLUMN_COUNT, - DEFAULT_PANEL_HEIGHT, - DEFAULT_PANEL_WIDTH, -} from './dashboard_constants'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/types.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/types.ts deleted file mode 100644 index 6e5257f0d1ee0b..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/types.ts +++ /dev/null @@ -1,34 +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 { PanelState, EmbeddableInput } from '../embeddable_api'; -export type PanelId = string; -export type SavedObjectId = string; - -export interface GridData { - w: number; - h: number; - x: number; - y: number; - i: string; -} - -export interface DashboardPanelState - extends PanelState { - readonly gridData: GridData; -} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable_api.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable_api.ts deleted file mode 100644 index 17911b908d3b3f..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable_api.ts +++ /dev/null @@ -1,20 +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. - */ - -export * from '../../../../../embeddable_api/public/np_ready/public'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/index.ts deleted file mode 100644 index 03ba836462c608..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/index.ts +++ /dev/null @@ -1,22 +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. - */ - -export * from './types'; -export * from './actions'; -export * from './embeddable'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts deleted file mode 100644 index fe6f4bcdafec92..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts +++ /dev/null @@ -1,75 +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 { IconType } from '@elastic/eui'; -import { - SavedObject as SavedObjectType, - SavedObjectAttributes, -} from '../../../../../../../core/server'; - -export interface DashboardCapabilities { - showWriteControls: boolean; - createNew: boolean; -} - -// TODO: Replace Saved object interfaces by the ones Core will provide when it is ready. -export type SavedObjectAttribute = - | string - | number - | boolean - | null - | undefined - | SavedObjectAttributes - | SavedObjectAttributes[]; - -export interface SimpleSavedObject { - attributes: T; - _version?: SavedObjectType['version']; - id: SavedObjectType['id']; - type: SavedObjectType['type']; - migrationVersion: SavedObjectType['migrationVersion']; - error: SavedObjectType['error']; - references: SavedObjectType['references']; - get(key: string): any; - set(key: string, value: any): T; - has(key: string): boolean; - save(): Promise>; - delete(): void; -} - -export interface SavedObjectMetaData { - type: string; - name: string; - getIconForSavedObject(savedObject: SimpleSavedObject): IconType; - getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; - showSavedObject?(savedObject: SimpleSavedObject): boolean; -} - -export interface Field { - name: string; - type: string; - // esTypes might be undefined on old index patterns that have not been refreshed since we added - // this prop. It is also undefined on scripted fields. - esTypes?: string[]; - aggregatable: boolean; - filterable: boolean; - searchable: boolean; - parent?: string; - subType?: string; -} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts deleted file mode 100644 index 3d243ab3aa37a0..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts +++ /dev/null @@ -1,73 +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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { IUiActionsSetup, IUiActionsStart } from '../../../../../../plugins/ui_actions/public'; -import { CONTEXT_MENU_TRIGGER, Plugin as EmbeddablePlugin } from './lib/embeddable_api'; -import { ExpandPanelAction, DashboardContainerFactory } from './lib'; -import { Start as InspectorStartContract } from '../../../../../../plugins/inspector/public'; - -interface SetupDependencies { - embeddable: ReturnType; - uiActions: IUiActionsSetup; -} - -interface StartDependencies { - embeddable: ReturnType; - inspector: InspectorStartContract; - uiActions: IUiActionsStart; - __LEGACY: { - SavedObjectFinder: React.ComponentType; - ExitFullScreenButton: React.ComponentType; - }; -} - -export type Setup = void; -export type Start = void; - -export class DashboardEmbeddableContainerPublicPlugin - implements Plugin { - constructor(initializerContext: PluginInitializerContext) {} - - public setup(core: CoreSetup, { embeddable, uiActions }: SetupDependencies): Setup { - const expandPanelAction = new ExpandPanelAction(); - uiActions.registerAction(expandPanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); - } - - public start(core: CoreStart, plugins: StartDependencies): Start { - const { application, notifications, overlays } = core; - const { embeddable, inspector, __LEGACY, uiActions } = plugins; - - const factory = new DashboardContainerFactory({ - application, - notifications, - overlays, - embeddable, - inspector, - SavedObjectFinder: __LEGACY.SavedObjectFinder, - ExitFullScreenButton: __LEGACY.ExitFullScreenButton, - uiActions, - }); - - embeddable.registerEmbeddableFactory(factory.type, factory); - } - - public stop() {} -} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/tests/dashboard_container.test.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/tests/dashboard_container.test.tsx deleted file mode 100644 index 6cf409581b76d0..00000000000000 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/tests/dashboard_container.test.tsx +++ /dev/null @@ -1,131 +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. - */ - -// @ts-ignore -import { findTestSubject } from '@elastic/eui/lib/test'; -import React from 'react'; -import { mount } from 'enzyme'; -import { nextTick } from 'test_utils/enzyme_helpers'; -import { I18nProvider } from '@kbn/i18n/react'; -import { ViewMode, CONTEXT_MENU_TRIGGER, EmbeddablePanel } from '../lib/embeddable_api'; -import { - DashboardContainer, - DashboardContainerOptions, -} from '../lib/embeddable/dashboard_container'; -import { getSampleDashboardInput } from '../lib/test_helpers'; -import { - CONTACT_CARD_EMBEDDABLE, - ContactCardEmbeddableFactory, -} from '../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; -import { - ContactCardEmbeddableInput, - ContactCardEmbeddable, - ContactCardEmbeddableOutput, -} from '../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; -import { embeddablePluginMock } from '../../../../../embeddable_api/public/np_ready/public/mocks'; -import { createEditModeAction } from '../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; -// eslint-disable-next-line -import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks'; -import { KibanaContextProvider } from '../../../../../../../plugins/kibana_react/public'; -// eslint-disable-next-line -import { uiActionsPluginMock } from 'src/plugins/ui_actions/public/mocks'; - -test('DashboardContainer in edit mode shows edit mode actions', async () => { - const inspector = inspectorPluginMock.createStartContract(); - const { setup, doStart } = embeddablePluginMock.createInstance(); - const uiActionsSetup = uiActionsPluginMock.createSetupContract(); - - const editModeAction = createEditModeAction(); - uiActionsSetup.registerAction(editModeAction); - uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction.id); - setup.registerEmbeddableFactory( - CONTACT_CARD_EMBEDDABLE, - new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) - ); - - const start = doStart(); - - const initialInput = getSampleDashboardInput({ viewMode: ViewMode.VIEW }); - const options: DashboardContainerOptions = { - application: {} as any, - embeddable: start, - notifications: {} as any, - overlays: {} as any, - inspector: {} as any, - SavedObjectFinder: () => null, - ExitFullScreenButton: () => null, - uiActions: {} as any, - }; - const container = new DashboardContainer(initialInput, options); - - const embeddable = await container.addNewEmbeddable< - ContactCardEmbeddableInput, - ContactCardEmbeddableOutput, - ContactCardEmbeddable - >(CONTACT_CARD_EMBEDDABLE, { - firstName: 'Bob', - }); - - const component = mount( - - - Promise.resolve([])} - getAllEmbeddableFactories={(() => []) as any} - getEmbeddableFactory={(() => null) as any} - notifications={{} as any} - overlays={{} as any} - inspector={inspector} - SavedObjectFinder={() => null} - /> - - - ); - - const button = findTestSubject(component, 'embeddablePanelToggleMenuIcon'); - - expect(button.length).toBe(1); - findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); - - expect(findTestSubject(component, `embeddablePanelContextMenuOpen`).length).toBe(1); - - const editAction = findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`); - - expect(editAction.length).toBe(0); - - container.updateInput({ viewMode: ViewMode.EDIT }); - await nextTick(); - component.update(); - findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); - await nextTick(); - component.update(); - expect(findTestSubject(component, 'embeddablePanelContextMenuOpen').length).toBe(0); - findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); - await nextTick(); - component.update(); - expect(findTestSubject(component, 'embeddablePanelContextMenuOpen').length).toBe(1); - - await nextTick(); - component.update(); - - // TODO: Address this. - // const action = findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`); - // expect(action.length).toBe(1); -}); diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index c436e1d45c6d86..abe9ec6d6e8731 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -18,21 +18,25 @@ */ import { i18n } from '@kbn/i18n'; -import { Filter } from '@kbn/es-query'; -import { npStart } from 'ui/new_platform'; +import { CoreStart } from 'src/core/public'; import { IAction, createAction, IncompatibleActionError, } from '../../../../../../plugins/ui_actions/public'; -import { changeTimeFilter, extractTimeFilter, FilterManager } from '../filter_manager'; -import { TimefilterContract } from '../../timefilter'; +import { + esFilters, + FilterManager, + TimefilterContract, + changeTimeFilter, + extractTimeFilter, +} from '../../../../../../plugins/data/public'; import { applyFiltersPopover } from '../apply_filters/apply_filters_popover'; import { IndexPatternsStart } from '../../index_patterns'; export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION'; interface ActionContext { - filters: Filter[]; + filters: esFilters.Filter[]; timeFieldName?: string; } @@ -41,6 +45,7 @@ async function isCompatible(context: ActionContext) { } export function createFilterAction( + overlays: CoreStart['overlays'], filterManager: FilterManager, timeFilter: TimefilterContract, indexPatternsService: IndexPatternsStart @@ -63,7 +68,7 @@ export function createFilterAction( throw new IncompatibleActionError(); } - let selectedFilters: Filter[] = filters; + let selectedFilters: esFilters.Filter[] = filters; if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( @@ -72,8 +77,8 @@ export function createFilterAction( }) ); - const filterSelectionPromise: Promise = new Promise(resolve => { - const overlay = npStart.core.overlays.openModal( + const filterSelectionPromise: Promise = new Promise(resolve => { + const overlay = overlays.openModal( applyFiltersPopover( filters, indexPatterns, @@ -81,7 +86,7 @@ export function createFilterAction( overlay.close(); resolve([]); }, - (filterSelection: Filter[]) => { + (filterSelection: esFilters.Filter[]) => { overlay.close(); resolve(filterSelection); } diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx index 5f7fbc19964335..e9d05d6340e584 100644 --- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx +++ b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx @@ -28,19 +28,18 @@ import { EuiModalHeaderTitle, EuiSwitch, } from '@elastic/eui'; -import { Filter } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { IndexPattern } from '../../index_patterns'; import { getFilterDisplayText } from '../filter_bar/filter_editor/lib/get_filter_display_text'; -import { mapAndFlattenFilters } from '../filter_manager/lib/map_and_flatten_filters'; +import { mapAndFlattenFilters, esFilters } from '../../../../../../plugins/data/public'; import { getDisplayValueFromFilter } from '../filter_bar/filter_editor/lib/get_display_value'; interface Props { - filters: Filter[]; + filters: esFilters.Filter[]; indexPatterns: IndexPattern[]; onCancel: () => void; - onSubmit: (filters: Filter[]) => void; + onSubmit: (filters: esFilters.Filter[]) => void; } interface State { @@ -58,7 +57,7 @@ export class ApplyFiltersPopoverContent extends Component { isFilterSelected: props.filters.map(() => true), }; } - private getLabel(filter: Filter) { + private getLabel(filter: esFilters.Filter) { const filterDisplayValue = getDisplayValueFromFilter(filter, this.props.indexPatterns); return getFilterDisplayText(filter, filterDisplayValue); } diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx index 0687701429866d..41f757e726c40b 100644 --- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx +++ b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx @@ -18,15 +18,15 @@ */ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; -import { Filter } from '@kbn/es-query'; import React, { Component } from 'react'; import { ApplyFiltersPopoverContent } from './apply_filter_popover_content'; import { IndexPattern } from '../../index_patterns/index_patterns'; +import { esFilters } from '../../../../../../plugins/data/public'; interface Props { - filters: Filter[]; + filters: esFilters.Filter[]; onCancel: () => void; - onSubmit: (filters: Filter[]) => void; + onSubmit: (filters: esFilters.Filter[]) => void; indexPatterns: IndexPattern[]; } @@ -56,9 +56,9 @@ export class ApplyFiltersPopover extends Component { } type cancelFunction = () => void; -type submitFunction = (filters: Filter[]) => void; +type submitFunction = (filters: esFilters.Filter[]) => void; export const applyFiltersPopover = ( - filters: Filter[], + filters: esFilters.Filter[], indexPatterns: IndexPattern[], onCancel: cancelFunction, onSubmit: submitFunction diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss index 3c90e18aecd5d6..1c47c28097454e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss @@ -11,6 +11,10 @@ margin-top: $euiSizeXS; } +.globalFilterBar__addButton { + min-height: $euiSizeL + $euiSizeXS; // same height as the badges +} + // sass-lint:disable quotes .globalFilterGroup__branch { padding: $euiSizeS $euiSizeM 0 0; @@ -40,4 +44,3 @@ margin-top: $euiSize * -1; } } - diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss index caf3b0b796b9e2..84538a62ca005f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss @@ -1,5 +1,5 @@ -@import '@elastic/eui/src/components/form/mixins'; @import '@elastic/eui/src/components/form/variables'; +@import '@elastic/eui/src/components/form/mixins'; /** * 1. Allow wrapping of long filter items @@ -19,11 +19,17 @@ &:not(.globalFilterItem-isDisabled) { @include euiFormControlDefaultShadow; + box-shadow: #{$euiFormControlBoxShadow}, inset 0 0 0 1px $kbnGlobalFilterItemBorderColor; // Make the actual border more visible + } + + &:focus-within { + animation: none !important; // Remove focus ring animation otherwise it overrides simulated border via box-shadow } } .globalFilterItem-isDisabled { - background-color: transparentize($euiColorLightShade, .4); + color: $euiColorDarkShade; + background-color: transparentize($euiColorLightShade, 0.5); text-decoration: line-through; font-weight: $euiFontWeightRegular; font-style: italic; @@ -39,12 +45,22 @@ bottom: 0; left: 0; width: $euiSizeXS; - background-color: $euiColorVis0; + background-color: $kbnGlobalFilterItemBorderColor; border-top-left-radius: $euiBorderRadius / 2; border-bottom-left-radius: $euiBorderRadius / 2; } } +.globalFilterItem-isExcluded { + &:not(.globalFilterItem-isDisabled) { + box-shadow: #{$euiFormControlBoxShadow}, inset 0 0 0 1px $kbnGlobalFilterItemBorderColorExcluded; + + &::before { + background-color: $kbnGlobalFilterItemPinnedColorExcluded; + } + } +} + .globalFilterItem__editorForm { padding: $euiSizeM; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss index 3c57b7fe2ca3ad..5333aff8b87da3 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_index.scss @@ -1,2 +1,3 @@ +@import 'variables'; @import 'global_filter_group'; @import 'global_filter_item'; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_variables.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_variables.scss new file mode 100644 index 00000000000000..3a9a0df4332c81 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_variables.scss @@ -0,0 +1,3 @@ +$kbnGlobalFilterItemBorderColor: tintOrShade($euiColorMediumShade, 35%, 20%); +$kbnGlobalFilterItemBorderColorExcluded: tintOrShade($euiColorDanger, 70%, 50%); +$kbnGlobalFilterItemPinnedColorExcluded: tintOrShade($euiColorDanger, 30%, 20%); diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index 066adb1e3275e8..333e1e328651d4 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -18,16 +18,6 @@ */ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; -import { - buildEmptyFilter, - disableFilter, - enableFilter, - Filter, - pinFilter, - toggleFilterDisabled, - toggleFilterNegated, - unpinFilter, -} from '@kbn/es-query'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { useState } from 'react'; @@ -38,10 +28,11 @@ import { FilterEditor } from './filter_editor'; import { FilterItem } from './filter_item'; import { FilterOptions } from './filter_options'; import { useKibana, KibanaContextProvider } from '../../../../../../plugins/kibana_react/public'; +import { esFilters } from '../../../../../../plugins/data/public'; interface Props { - filters: Filter[]; - onFiltersUpdated?: (filters: Filter[]) => void; + filters: esFilters.Filter[]; + onFiltersUpdated?: (filters: esFilters.Filter[]) => void; className: string; indexPatterns: IndexPattern[]; intl: InjectedIntl; @@ -87,7 +78,7 @@ function FilterBarUI(props: Props) { return content; } - function onFiltersUpdated(filters: Filter[]) { + function onFiltersUpdated(filters: esFilters.Filter[]) { if (props.onFiltersUpdated) { props.onFiltersUpdated(filters); } @@ -112,13 +103,14 @@ function FilterBarUI(props: Props) { const isPinned = uiSettings!.get('filters:pinnedByDefault'); const [indexPattern] = props.indexPatterns; const index = indexPattern && indexPattern.id; - const newFilter = buildEmptyFilter(isPinned, index); + const newFilter = esFilters.buildEmptyFilter(isPinned, index); const button = ( setIsAddFilterPopoverOpen(true)} data-test-subj="addFilter" + className="globalFilterBar__addButton" > +{' '} void; + onSubmit: (filter: esFilters.Filter) => void; onCancel: () => void; intl: InjectedIntl; } @@ -379,7 +379,9 @@ class FilterEditorUI extends Component { private getFieldFromFilter() { const indexPattern = this.getIndexPatternFromFilter(); - return indexPattern && getFieldFromFilter(this.props.filter as FieldFilter, indexPattern); + return ( + indexPattern && getFieldFromFilter(this.props.filter as esFilters.FieldFilter, indexPattern) + ); } private getSelectedOperator() { diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts index 734c5d00e58d5d..dbff5096f2287d 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts @@ -17,7 +17,6 @@ * under the License. */ -import { FilterStateStore, toggleFilterNegated } from '@kbn/es-query'; import { mockFields, mockIndexPattern } from '../../../../index_patterns'; import { IndexPattern, Field } from '../../../../index'; import { @@ -42,6 +41,7 @@ import { existsFilter } from './fixtures/exists_filter'; import { phraseFilter } from './fixtures/phrase_filter'; import { phrasesFilter } from './fixtures/phrases_filter'; import { rangeFilter } from './fixtures/range_filter'; +import { esFilters } from '../../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); @@ -81,7 +81,7 @@ describe('Filter editor utils', () => { }); it('should return "is not" for phrase filter', () => { - const negatedPhraseFilter = toggleFilterNegated(phraseFilter); + const negatedPhraseFilter = esFilters.toggleFilterNegated(phraseFilter); const operator = getOperatorFromFilter(negatedPhraseFilter); expect(operator).not.toBeUndefined(); expect(operator && operator.type).toBe('phrase'); @@ -96,7 +96,7 @@ describe('Filter editor utils', () => { }); it('should return "is not one of" for negated phrases filter', () => { - const negatedPhrasesFilter = toggleFilterNegated(phrasesFilter); + const negatedPhrasesFilter = esFilters.toggleFilterNegated(phrasesFilter); const operator = getOperatorFromFilter(negatedPhrasesFilter); expect(operator).not.toBeUndefined(); expect(operator && operator.type).toBe('phrases'); @@ -111,7 +111,7 @@ describe('Filter editor utils', () => { }); it('should return "is not between" for negated range filter', () => { - const negatedRangeFilter = toggleFilterNegated(rangeFilter); + const negatedRangeFilter = esFilters.toggleFilterNegated(rangeFilter); const operator = getOperatorFromFilter(negatedRangeFilter); expect(operator).not.toBeUndefined(); expect(operator && operator.type).toBe('range'); @@ -126,7 +126,7 @@ describe('Filter editor utils', () => { }); it('should return "does not exists" for negated exists filter', () => { - const negatedExistsFilter = toggleFilterNegated(existsFilter); + const negatedExistsFilter = esFilters.toggleFilterNegated(existsFilter); const operator = getOperatorFromFilter(negatedExistsFilter); expect(operator).not.toBeUndefined(); expect(operator && operator.type).toBe('exists'); @@ -246,7 +246,7 @@ describe('Filter editor utils', () => { it('should build phrase filters', () => { const params = 'foo'; const alias = 'bar'; - const state = FilterStateStore.APP_STATE; + const state = esFilters.FilterStateStore.APP_STATE; const filter = buildFilter( mockedIndexPattern, mockedFields[0], @@ -268,7 +268,7 @@ describe('Filter editor utils', () => { it('should build phrases filters', () => { const params = ['foo', 'bar']; const alias = 'bar'; - const state = FilterStateStore.APP_STATE; + const state = esFilters.FilterStateStore.APP_STATE; const filter = buildFilter( mockedIndexPattern, mockedFields[0], @@ -290,7 +290,7 @@ describe('Filter editor utils', () => { it('should build range filters', () => { const params = { from: 'foo', to: 'qux' }; const alias = 'bar'; - const state = FilterStateStore.APP_STATE; + const state = esFilters.FilterStateStore.APP_STATE; const filter = buildFilter( mockedIndexPattern, mockedFields[0], @@ -311,7 +311,7 @@ describe('Filter editor utils', () => { it('should build exists filters', () => { const params = undefined; const alias = 'bar'; - const state = FilterStateStore.APP_STATE; + const state = esFilters.FilterStateStore.APP_STATE; const filter = buildFilter( mockedIndexPattern, mockedFields[0], @@ -332,7 +332,7 @@ describe('Filter editor utils', () => { it('should include disabled state', () => { const params = undefined; const alias = 'bar'; - const state = FilterStateStore.APP_STATE; + const state = esFilters.FilterStateStore.APP_STATE; const filter = buildFilter( mockedIndexPattern, mockedFields[0], @@ -348,7 +348,7 @@ describe('Filter editor utils', () => { it('should negate based on operator', () => { const params = undefined; const alias = 'bar'; - const state = FilterStateStore.APP_STATE; + const state = esFilters.FilterStateStore.APP_STATE; const filter = buildFilter( mockedIndexPattern, mockedFields[0], diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts index f0628f03c173e1..b7d20526a6b924 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts @@ -18,42 +18,30 @@ */ import dateMath from '@elastic/datemath'; -import { - buildExistsFilter, - buildPhraseFilter, - buildPhrasesFilter, - buildRangeFilter, - FieldFilter, - Filter, - FilterMeta, - FilterStateStore, - PhraseFilter, - PhrasesFilter, - RangeFilter, -} from '@kbn/es-query'; import { omit } from 'lodash'; import { Ipv4Address } from '../../../../../../../../plugins/kibana_utils/public'; import { Field, IndexPattern, isFilterable } from '../../../../index_patterns'; import { FILTER_OPERATORS, Operator } from './filter_operators'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export function getIndexPatternFromFilter( - filter: Filter, + filter: esFilters.Filter, indexPatterns: IndexPattern[] ): IndexPattern | undefined { return indexPatterns.find(indexPattern => indexPattern.id === filter.meta.index); } -export function getFieldFromFilter(filter: FieldFilter, indexPattern: IndexPattern) { +export function getFieldFromFilter(filter: esFilters.FieldFilter, indexPattern: IndexPattern) { return indexPattern.fields.find(field => field.name === filter.meta.key); } -export function getOperatorFromFilter(filter: Filter) { +export function getOperatorFromFilter(filter: esFilters.Filter) { return FILTER_OPERATORS.find(operator => { return filter.meta.type === operator.type && filter.meta.negate === operator.negate; }); } -export function getQueryDslFromFilter(filter: Filter) { +export function getQueryDslFromFilter(filter: esFilters.Filter) { return omit(filter, ['$state', 'meta']); } @@ -67,16 +55,16 @@ export function getOperatorOptions(field: Field) { }); } -export function getFilterParams(filter: Filter) { +export function getFilterParams(filter: esFilters.Filter) { switch (filter.meta.type) { case 'phrase': - return (filter as PhraseFilter).meta.params.query; + return (filter as esFilters.PhraseFilter).meta.params.query; case 'phrases': - return (filter as PhrasesFilter).meta.params; + return (filter as esFilters.PhrasesFilter).meta.params; case 'range': return { - from: (filter as RangeFilter).meta.params.gte, - to: (filter as RangeFilter).meta.params.lt, + from: (filter as esFilters.RangeFilter).meta.params.gte, + to: (filter as esFilters.RangeFilter).meta.params.lt, }; } } @@ -133,8 +121,8 @@ export function buildFilter( disabled: boolean, params: any, alias: string | null, - store: FilterStateStore -): Filter { + store: esFilters.FilterStateStore +): esFilters.Filter { const filter = buildBaseFilter(indexPattern, field, operator, params); filter.meta.alias = alias; filter.meta.negate = operator.negate; @@ -148,17 +136,17 @@ function buildBaseFilter( field: Field, operator: Operator, params: any -): Filter { +): esFilters.Filter { switch (operator.type) { case 'phrase': - return buildPhraseFilter(field, params, indexPattern); + return esFilters.buildPhraseFilter(field, params, indexPattern); case 'phrases': - return buildPhrasesFilter(field, params, indexPattern); + return esFilters.buildPhrasesFilter(field, params, indexPattern); case 'range': const newParams = { gte: params.from, lt: params.to }; - return buildRangeFilter(field, newParams, indexPattern); + return esFilters.buildRangeFilter(field, newParams, indexPattern); case 'exists': - return buildExistsFilter(field, indexPattern); + return esFilters.buildExistsFilter(field, indexPattern); default: throw new Error(`Unknown operator type: ${operator.type}`); } @@ -170,10 +158,10 @@ export function buildCustomFilter( disabled: boolean, negate: boolean, alias: string | null, - store: FilterStateStore -): Filter { - const meta: FilterMeta = { index, type: 'custom', disabled, negate, alias }; - const filter: Filter = { ...queryDsl, meta }; + store: esFilters.FilterStateStore +): esFilters.Filter { + const meta: esFilters.FilterMeta = { index, type: 'custom', disabled, negate, alias }; + const filter: esFilters.Filter = { ...queryDsl, meta }; filter.$state = { store }; return filter; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/exists_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/exists_filter.ts index a17f767006f3ea..5af97818f9bfbd 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/exists_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/exists_filter.ts @@ -17,9 +17,9 @@ * under the License. */ -import { ExistsFilter, FilterStateStore } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../../../plugins/data/public'; -export const existsFilter: ExistsFilter = { +export const existsFilter: esFilters.ExistsFilter = { meta: { index: 'logstash-*', negate: false, @@ -29,6 +29,6 @@ export const existsFilter: ExistsFilter = { alias: null, }, $state: { - store: FilterStateStore.APP_STATE, + store: esFilters.FilterStateStore.APP_STATE, }, }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts index 77bb8e06c801ad..b6c8b9905e6b33 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts @@ -17,9 +17,9 @@ * under the License. */ -import { FilterStateStore, PhraseFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../../../plugins/data/public'; -export const phraseFilter: PhraseFilter = { +export const phraseFilter: esFilters.PhraseFilter = { meta: { negate: false, index: 'logstash-*', @@ -33,6 +33,6 @@ export const phraseFilter: PhraseFilter = { }, }, $state: { - store: FilterStateStore.APP_STATE, + store: esFilters.FilterStateStore.APP_STATE, }, }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts index e86c3ee1318e34..2e2ba4f798bddf 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts @@ -17,9 +17,9 @@ * under the License. */ -import { FilterStateStore, PhrasesFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../../../plugins/data/public'; -export const phrasesFilter: PhrasesFilter = { +export const phrasesFilter: esFilters.PhrasesFilter = { meta: { index: 'logstash-*', type: 'phrases', @@ -31,6 +31,6 @@ export const phrasesFilter: PhrasesFilter = { alias: null, }, $state: { - store: FilterStateStore.APP_STATE, + store: esFilters.FilterStateStore.APP_STATE, }, }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts index 46a5181450feae..c6438e30ecec61 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts @@ -17,9 +17,9 @@ * under the License. */ -import { FilterStateStore, RangeFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../../../plugins/data/public'; -export const rangeFilter: RangeFilter = { +export const rangeFilter: esFilters.RangeFilter = { meta: { index: 'logstash-*', negate: false, @@ -34,7 +34,7 @@ export const rangeFilter: RangeFilter = { }, }, $state: { - store: FilterStateStore.APP_STATE, + store: esFilters.FilterStateStore.APP_STATE, }, range: {}, }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts index 551b99d01b7da8..d8af7b3e97ad23 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../../plugins/data/public'; import { IndexPattern } from '../../../../index_patterns/index_patterns'; import { Field } from '../../../../index_patterns/fields'; import { getIndexPatternFromFilter } from './filter_editor_utils'; @@ -33,7 +33,10 @@ function getValueFormatter(indexPattern?: IndexPattern, key?: string) { return format; } -export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IndexPattern[]): string { +export function getDisplayValueFromFilter( + filter: esFilters.Filter, + indexPatterns: IndexPattern[] +): string { const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); if (typeof filter.meta.value === 'function') { diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.ts deleted file mode 100644 index 73ee1a69a2ce38..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.ts +++ /dev/null @@ -1,53 +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 { Filter } from '@kbn/es-query'; -import { i18n } from '@kbn/i18n'; -import { existsOperator, isOneOfOperator } from './filter_operators'; - -export function getFilterDisplayText(filter: Filter, filterDisplayName: string) { - const prefix = filter.meta.negate - ? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', { - defaultMessage: 'NOT ', - })}` - : ''; - - if (filter.meta.alias !== null) { - return `${prefix}${filter.meta.alias}`; - } - - switch (filter.meta.type) { - case 'exists': - return `${prefix}${filter.meta.key} ${existsOperator.message}`; - case 'geo_bounding_box': - return `${prefix}${filter.meta.key}: ${filterDisplayName}`; - case 'geo_polygon': - return `${prefix}${filter.meta.key}: ${filterDisplayName}`; - case 'phrase': - return `${prefix}${filter.meta.key}: ${filterDisplayName}`; - case 'phrases': - return `${prefix}${filter.meta.key} ${isOneOfOperator.message} ${filterDisplayName}`; - case 'query_string': - return `${prefix}${filterDisplayName}`; - case 'range': - return `${prefix}${filter.meta.key}: ${filterDisplayName}`; - default: - return `${prefix}${JSON.stringify(filter.query)}`; - } -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.tsx new file mode 100644 index 00000000000000..21abcd8510046d --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.tsx @@ -0,0 +1,102 @@ +/* + * 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, { Fragment } from 'react'; +import { EuiTextColor } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { existsOperator, isOneOfOperator } from './filter_operators'; +import { esFilters } from '../../../../../../../../plugins/data/public'; + +export function getFilterDisplayText(filter: esFilters.Filter, filterDisplayName: string) { + const prefixText = filter.meta.negate + ? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', { + defaultMessage: 'NOT ', + })}` + : ''; + const prefix = + filter.meta.negate && !filter.meta.disabled ? ( + {prefixText} + ) : ( + prefixText + ); + + if (filter.meta.alias !== null) { + return `${prefix}${filter.meta.alias}`; + } + + switch (filter.meta.type) { + case 'exists': + return ( + + {prefix} + {filter.meta.key} {existsOperator.message} + + ); + case 'geo_bounding_box': + return ( + + {prefix} + {filter.meta.key}: {filterDisplayName} + + ); + case 'geo_polygon': + return ( + + {prefix} + {filter.meta.key}: {filterDisplayName} + + ); + case 'phrase': + return ( + + {prefix} + {filter.meta.key}: {filterDisplayName} + + ); + case 'phrases': + return ( + + {prefix} + {filter.meta.key} {isOneOfOperator.message} {filterDisplayName} + + ); + case 'query_string': + return ( + + {prefix} + {filterDisplayName} + + ); + case 'range': + case 'phrase': + return ( + + {prefix} + {filter.meta.key}: {filterDisplayName} + + ); + default: + return ( + + {prefix} + {JSON.stringify(filter.query)} + + ); + } +} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx index 2e98cbd306e9c6..50c1672333801e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx @@ -18,13 +18,6 @@ */ import { EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { - Filter, - isFilterPinned, - toggleFilterDisabled, - toggleFilterNegated, - toggleFilterPinned, -} from '@kbn/es-query'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { Component } from 'react'; @@ -33,13 +26,14 @@ import { IndexPattern } from '../../index_patterns'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; import { getDisplayValueFromFilter } from './filter_editor/lib/get_display_value'; +import { esFilters } from '../../../../../../plugins/data/public'; interface Props { id: string; - filter: Filter; + filter: esFilters.Filter; indexPatterns: IndexPattern[]; className?: string; - onUpdate: (filter: Filter) => void; + onUpdate: (filter: esFilters.Filter) => void; onRemove: () => void; intl: InjectedIntl; uiSettings: UiSettingsClientContract; @@ -62,7 +56,7 @@ class FilterItemUI extends Component { 'globalFilterItem', { 'globalFilterItem-isDisabled': disabled, - 'globalFilterItem-isPinned': isFilterPinned(filter), + 'globalFilterItem-isPinned': esFilters.isFilterPinned(filter), 'globalFilterItem-isExcluded': negate, }, this.props.className @@ -91,7 +85,7 @@ class FilterItemUI extends Component { id: 0, items: [ { - name: isFilterPinned(filter) + name: esFilters.isFilterPinned(filter) ? this.props.intl.formatMessage({ id: 'data.filter.filterBar.unpinFilterButtonLabel', defaultMessage: 'Unpin', @@ -209,23 +203,23 @@ class FilterItemUI extends Component { }); }; - private onSubmit = (filter: Filter) => { + private onSubmit = (filter: esFilters.Filter) => { this.closePopover(); this.props.onUpdate(filter); }; private onTogglePinned = () => { - const filter = toggleFilterPinned(this.props.filter); + const filter = esFilters.toggleFilterPinned(this.props.filter); this.props.onUpdate(filter); }; private onToggleNegated = () => { - const filter = toggleFilterNegated(this.props.filter); + const filter = esFilters.toggleFilterNegated(this.props.filter); this.props.onUpdate(filter); }; private onToggleDisabled = () => { - const filter = toggleFilterDisabled(this.props.filter); + const filter = esFilters.toggleFilterDisabled(this.props.filter); this.props.onUpdate(filter); }; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx index a7ea23efce49ed..6421691c4ef416 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx @@ -17,14 +17,14 @@ * under the License. */ -import { EuiBadge } from '@elastic/eui'; -import { Filter, isFilterPinned } from '@kbn/es-query'; +import { EuiBadge, useInnerText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { SFC } from 'react'; import { getFilterDisplayText } from '../filter_editor/lib/get_filter_display_text'; +import { esFilters } from '../../../../../../../plugins/data/public'; interface Props { - filter: Filter; + filter: esFilters.Filter; displayName: string; [propName: string]: any; } @@ -36,12 +36,15 @@ export const FilterView: SFC = ({ displayName, ...rest }: Props) => { + const [ref, innerText] = useInnerText(); + const displayText = {getFilterDisplayText(filter, displayName)}; + let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { - defaultMessage: 'Filter: {displayText}. Select for more filter actions.', - values: { displayText: getFilterDisplayText(filter, displayName) }, + defaultMessage: 'Filter: {innerText}. Select for more filter actions.', + values: { innerText }, }); - if (isFilterPinned(filter)) { + if (esFilters.isFilterPinned(filter)) { title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', { defaultMessage: 'Pinned', })} ${title}`; @@ -72,7 +75,7 @@ export const FilterView: SFC = ({ })} {...rest} > - {getFilterDisplayText(filter, displayName)} + {displayText} ); }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.test.ts deleted file mode 100644 index 21c51c9f68f41d..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.test.ts +++ /dev/null @@ -1,686 +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 _ from 'lodash'; -import sinon from 'sinon'; - -import { Subscription } from 'rxjs'; -import { Filter, FilterStateStore } from '@kbn/es-query'; - -import { FilterStateManager } from './filter_state_manager'; -import { FilterManager } from './filter_manager'; - -import { getFilter } from './test_helpers/get_stub_filter'; -import { StubState } from './test_helpers/stub_state'; -import { getFiltersArray } from './test_helpers/get_filters_array'; - -import { coreMock } from '../../../../../../core/public/mocks'; -const setupMock = coreMock.createSetup(); - -setupMock.uiSettings.get.mockImplementation((key: string) => { - return true; -}); - -describe('filter_manager', () => { - let appStateStub: StubState; - let globalStateStub: StubState; - - let updateSubscription: Subscription | undefined; - let fetchSubscription: Subscription | undefined; - let updateListener: sinon.SinonSpy; - - let filterManager: FilterManager; - let readyFilters: Filter[]; - - beforeEach(() => { - updateListener = sinon.stub(); - appStateStub = new StubState(); - globalStateStub = new StubState(); - filterManager = new FilterManager(setupMock.uiSettings); - readyFilters = getFiltersArray(); - - // FilterStateManager is tested indirectly. - // Therefore, we don't need it's instance. - new FilterStateManager( - globalStateStub, - () => { - return appStateStub; - }, - filterManager - ); - }); - - afterEach(async () => { - if (updateSubscription) { - updateSubscription.unsubscribe(); - } - if (fetchSubscription) { - fetchSubscription.unsubscribe(); - } - - filterManager.removeAll(); - }); - - describe('observing', () => { - test('should return observable', () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - fetchSubscription = filterManager.getUpdates$().subscribe(() => {}); - expect(updateSubscription).toBeInstanceOf(Subscription); - expect(fetchSubscription).toBeInstanceOf(Subscription); - }); - - test('should observe global state', done => { - updateSubscription = filterManager.getUpdates$().subscribe(() => { - expect(filterManager.getGlobalFilters()).toHaveLength(1); - if (updateSubscription) { - updateSubscription.unsubscribe(); - } - done(); - }); - - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'age', 34); - globalStateStub.filters.push(f1); - }); - - test('should observe app state', done => { - updateSubscription = filterManager.getUpdates$().subscribe(() => { - expect(filterManager.getAppFilters()).toHaveLength(1); - if (updateSubscription) { - updateSubscription.unsubscribe(); - } - done(); - }); - - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - appStateStub.filters.push(f1); - }); - }); - - describe('get \\ set filters', () => { - test('should be empty', () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(0); - expect(filterManager.getFilters()).toHaveLength(0); - - const partitionedFiltres = filterManager.getPartitionedFilters(); - expect(partitionedFiltres.appFilters).toHaveLength(0); - expect(partitionedFiltres.globalFilters).toHaveLength(0); - expect(updateListener.called).toBeFalsy(); - }); - - test('app state should be set', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - filterManager.setFilters([f1]); - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(0); - expect(filterManager.getFilters()).toHaveLength(1); - - const partitionedFiltres = filterManager.getPartitionedFilters(); - expect(partitionedFiltres.appFilters).toHaveLength(1); - expect(partitionedFiltres.globalFilters).toHaveLength(0); - expect(updateListener.called).toBeTruthy(); - }); - - test('global state should be set', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - filterManager.setFilters([f1]); - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(filterManager.getFilters()).toHaveLength(1); - - const partitionedFiltres = filterManager.getPartitionedFilters(); - expect(partitionedFiltres.appFilters).toHaveLength(0); - expect(partitionedFiltres.globalFilters).toHaveLength(1); - expect(updateListener.called).toBeTruthy(); - }); - - test('both states should be set', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); - filterManager.setFilters([f1, f2]); - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(filterManager.getFilters()).toHaveLength(2); - - const partitionedFiltres = filterManager.getPartitionedFilters(); - expect(partitionedFiltres.appFilters).toHaveLength(1); - expect(partitionedFiltres.globalFilters).toHaveLength(1); - - // listener should be called just once - expect(updateListener.called).toBeTruthy(); - expect(updateListener.callCount).toBe(1); - }); - - test('set state should override previous state', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); - - filterManager.setFilters([f1]); - filterManager.setFilters([f2]); - - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(0); - expect(filterManager.getFilters()).toHaveLength(1); - - const partitionedFiltres = filterManager.getPartitionedFilters(); - expect(partitionedFiltres.appFilters).toHaveLength(1); - expect(partitionedFiltres.globalFilters).toHaveLength(0); - - // listener should be called just once - expect(updateListener.called).toBeTruthy(); - expect(updateListener.callCount).toBe(2); - }); - - test('changing a disabled filter should fire only update event', async function() { - const updateStub = jest.fn(); - const fetchStub = jest.fn(); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, true, false, 'age', 34); - - filterManager.setFilters([f1]); - - filterManager.getUpdates$().subscribe({ - next: updateStub, - }); - - filterManager.getFetches$().subscribe({ - next: fetchStub, - }); - - const f2 = _.cloneDeep(f1); - f2.meta.negate = true; - filterManager.setFilters([f2]); - - // this time, events should be emitted - expect(fetchStub).toBeCalledTimes(0); - expect(updateStub).toBeCalledTimes(1); - }); - }); - - describe('add filters', () => { - test('app state should accept a single filter', async function() { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - filterManager.addFilters(f1); - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(0); - expect(updateListener.callCount).toBe(1); - expect(appStateStub.filters.length).toBe(1); - }); - - test('app state should accept array', async () => { - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'female'); - filterManager.addFilters([f1]); - filterManager.addFilters([f2]); - expect(filterManager.getAppFilters()).toHaveLength(2); - expect(filterManager.getGlobalFilters()).toHaveLength(0); - expect(appStateStub.filters.length).toBe(2); - }); - - test('global state should accept a single filer', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - filterManager.addFilters(f1); - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(updateListener.callCount).toBe(1); - expect(globalStateStub.filters.length).toBe(1); - }); - - test('global state should be accept array', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'gender', 'female'); - filterManager.addFilters([f1, f2]); - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(2); - expect(globalStateStub.filters.length).toBe(2); - }); - - test('add multiple filters at once', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'gender', 'female'); - filterManager.addFilters([f1, f2]); - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(2); - expect(updateListener.callCount).toBe(1); - }); - - test('add same filter to global and app', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - filterManager.addFilters([f1, f2]); - - // FILTER SHOULD BE ADDED ONLY ONCE, TO GLOBAL - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(updateListener.callCount).toBe(1); - }); - - test('add same filter with different values to global and app', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 38); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - filterManager.addFilters([f1, f2]); - - // FILTER SHOULD BE ADDED TWICE - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(updateListener.callCount).toBe(1); - }); - - test('add filter with no state, and force pin', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 38); - f1.$state = undefined; - - filterManager.addFilters([f1], true); - - // FILTER SHOULD BE GLOBAL - const f1Output = filterManager.getFilters()[0]; - expect(f1Output.$state).toBeDefined(); - if (f1Output.$state) { - expect(f1Output.$state.store).toBe(FilterStateStore.GLOBAL_STATE); - } - }); - - test('add filter with no state, and dont force pin', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 38); - f1.$state = undefined; - - filterManager.addFilters([f1], false); - - // FILTER SHOULD BE APP - const f1Output = filterManager.getFilters()[0]; - expect(f1Output.$state).toBeDefined(); - if (f1Output.$state) { - expect(f1Output.$state.store).toBe(FilterStateStore.APP_STATE); - } - }); - - test('should return app and global filters', async function() { - const filters = getFiltersArray(); - filterManager.addFilters(filters[0], false); - filterManager.addFilters(filters[1], true); - - // global filters should be listed first - let res = filterManager.getFilters(); - expect(res).toHaveLength(2); - expect(res[0].$state && res[0].$state.store).toEqual(FilterStateStore.GLOBAL_STATE); - expect(res[0].meta.disabled).toEqual(filters[1].meta.disabled); - expect(res[0].query).toEqual(filters[1].query); - - expect(res[1].$state && res[1].$state.store).toEqual(FilterStateStore.APP_STATE); - expect(res[1].meta.disabled).toEqual(filters[0].meta.disabled); - expect(res[1].query).toEqual(filters[0].query); - - // should return updated version of filters - filterManager.addFilters(filters[2], false); - - res = filterManager.getFilters(); - expect(res).toHaveLength(3); - }); - - test('should skip appStateStub filters that match globalStateStub filters', async function() { - filterManager.addFilters(readyFilters, true); - const appFilter = _.cloneDeep(readyFilters[1]); - filterManager.addFilters(appFilter, false); - - // global filters should be listed first - const res = filterManager.getFilters(); - expect(res).toHaveLength(3); - _.each(res, function(filter) { - expect(filter.$state && filter.$state.store).toBe(FilterStateStore.GLOBAL_STATE); - }); - }); - - test('should allow overwriting a positive filter by a negated one', async function() { - // Add negate: false version of the filter - const filter = _.cloneDeep(readyFilters[0]); - filter.meta.negate = false; - - filterManager.addFilters(filter); - expect(filterManager.getFilters()).toHaveLength(1); - expect(filterManager.getFilters()[0]).toEqual(filter); - - // Add negate: true version of the same filter - const negatedFilter = _.cloneDeep(readyFilters[0]); - negatedFilter.meta.negate = true; - - filterManager.addFilters(negatedFilter); - // The negated filter should overwrite the positive one - expect(globalStateStub.filters.length).toBe(1); - expect(filterManager.getFilters()).toHaveLength(1); - expect(filterManager.getFilters()[0]).toEqual(negatedFilter); - }); - - test('should allow overwriting a negated filter by a positive one', async function() { - // Add negate: true version of the same filter - const negatedFilter = _.cloneDeep(readyFilters[0]); - negatedFilter.meta.negate = true; - - filterManager.addFilters(negatedFilter); - - // The negated filter should overwrite the positive one - expect(globalStateStub.filters.length).toBe(1); - expect(globalStateStub.filters[0]).toEqual(negatedFilter); - - // Add negate: false version of the filter - const filter = _.cloneDeep(readyFilters[0]); - filter.meta.negate = false; - - filterManager.addFilters(filter); - expect(globalStateStub.filters.length).toBe(1); - expect(globalStateStub.filters[0]).toEqual(filter); - }); - - test('should fire the update and fetch events', async function() { - const updateStub = jest.fn(); - const fetchStub = jest.fn(); - - filterManager.getUpdates$().subscribe({ - next: updateStub, - }); - - filterManager.getFetches$().subscribe({ - next: fetchStub, - }); - - filterManager.addFilters(readyFilters); - - // updates should trigger state saves - expect(appStateStub.save.callCount).toBe(1); - expect(globalStateStub.save.callCount).toBe(1); - - // this time, events should be emitted - expect(fetchStub).toBeCalledTimes(1); - expect(updateStub).toBeCalledTimes(1); - }); - }); - - describe('filter reconciliation', function() { - test('should de-dupe appStateStub filters being added', async function() { - const newFilter = _.cloneDeep(readyFilters[1]); - filterManager.addFilters(readyFilters, false); - expect(appStateStub.filters.length).toBe(3); - - filterManager.addFilters(newFilter, false); - expect(appStateStub.filters.length).toBe(3); - }); - - test('should de-dupe globalStateStub filters being added', async function() { - const newFilter = _.cloneDeep(readyFilters[1]); - filterManager.addFilters(readyFilters, true); - expect(globalStateStub.filters.length).toBe(3); - - filterManager.addFilters(newFilter, true); - expect(globalStateStub.filters.length).toBe(3); - }); - - test('should de-dupe globalStateStub filters being set', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = _.cloneDeep(f1); - filterManager.setFilters([f1, f2]); - expect(filterManager.getAppFilters()).toHaveLength(0); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(filterManager.getFilters()).toHaveLength(1); - }); - - test('should de-dupe appStateStub filters being set', async () => { - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = _.cloneDeep(f1); - filterManager.setFilters([f1, f2]); - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(0); - expect(filterManager.getFilters()).toHaveLength(1); - }); - - test('should mutate global filters on appStateStub filter changes', async function() { - const idx = 1; - filterManager.addFilters(readyFilters, true); - - const appFilter = _.cloneDeep(readyFilters[idx]); - appFilter.meta.negate = true; - appFilter.$state = { - store: FilterStateStore.APP_STATE, - }; - filterManager.addFilters(appFilter); - const res = filterManager.getFilters(); - expect(res).toHaveLength(3); - _.each(res, function(filter, i) { - expect(filter.$state && filter.$state.store).toBe('globalState'); - // make sure global filter actually mutated - expect(filter.meta.negate).toBe(i === idx); - }); - }); - - test('should merge conflicting appStateStub filters', async function() { - filterManager.addFilters(readyFilters, true); - const appFilter = _.cloneDeep(readyFilters[1]); - appFilter.meta.negate = true; - appFilter.$state = { - store: FilterStateStore.APP_STATE, - }; - filterManager.addFilters(appFilter, false); - - // global filters should be listed first - const res = filterManager.getFilters(); - expect(res).toHaveLength(3); - expect( - res.filter(function(filter) { - return filter.$state && filter.$state.store === FilterStateStore.GLOBAL_STATE; - }).length - ).toBe(3); - }); - - test('should enable disabled filters - global state', async function() { - // test adding to globalStateStub - const disabledFilters = _.map(readyFilters, function(filter) { - const f = _.cloneDeep(filter); - f.meta.disabled = true; - return f; - }); - filterManager.addFilters(disabledFilters, true); - filterManager.addFilters(readyFilters, true); - - const res = filterManager.getFilters(); - expect(res).toHaveLength(3); - expect( - res.filter(function(filter) { - return filter.meta.disabled === false; - }).length - ).toBe(3); - }); - - test('should enable disabled filters - app state', async function() { - // test adding to appStateStub - const disabledFilters = _.map(readyFilters, function(filter) { - const f = _.cloneDeep(filter); - f.meta.disabled = true; - return f; - }); - filterManager.addFilters(disabledFilters, true); - filterManager.addFilters(readyFilters, false); - - const res = filterManager.getFilters(); - expect(res).toHaveLength(3); - expect( - res.filter(function(filter) { - return filter.meta.disabled === false; - }).length - ).toBe(3); - }); - }); - - describe('remove filters', () => { - test('remove on empty should do nothing and not fire events', async () => { - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - filterManager.removeAll(); - expect(updateListener.called).toBeFalsy(); - expect(filterManager.getFilters()).toHaveLength(0); - }); - - test('remove on full should clean and fire events', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); - filterManager.setFilters([f1, f2]); - - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - filterManager.removeAll(); - expect(updateListener.called).toBeTruthy(); - expect(filterManager.getFilters()).toHaveLength(0); - }); - - test('remove non existing filter should do nothing and not fire events', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); - const f3 = getFilter(FilterStateStore.APP_STATE, false, false, 'country', 'US'); - filterManager.setFilters([f1, f2]); - expect(filterManager.getFilters()).toHaveLength(2); - - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - await filterManager.removeFilter(f3); - expect(updateListener.called).toBeFalsy(); - expect(filterManager.getFilters()).toHaveLength(2); - }); - - test('remove existing filter should remove and fire events', async () => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); - const f3 = getFilter(FilterStateStore.APP_STATE, false, false, 'country', 'US'); - filterManager.setFilters([f1, f2, f3]); - expect(filterManager.getFilters()).toHaveLength(3); - - updateSubscription = filterManager.getUpdates$().subscribe(updateListener); - await filterManager.removeFilter(f3); - expect(updateListener.called).toBeTruthy(); - expect(filterManager.getFilters()).toHaveLength(2); - }); - - test('should remove the filter from appStateStub', async function() { - filterManager.addFilters(readyFilters, false); - expect(appStateStub.filters).toHaveLength(3); - filterManager.removeFilter(readyFilters[0]); - expect(appStateStub.filters).toHaveLength(2); - }); - - test('should remove the filter from globalStateStub', async function() { - filterManager.addFilters(readyFilters, true); - expect(globalStateStub.filters).toHaveLength(3); - filterManager.removeFilter(readyFilters[0]); - expect(globalStateStub.filters).toHaveLength(2); - }); - - test('should fire the update and fetch events', async function() { - const updateStub = jest.fn(); - const fetchStub = jest.fn(); - - filterManager.addFilters(readyFilters, false); - - filterManager.getUpdates$().subscribe({ - next: updateStub, - }); - - filterManager.getFetches$().subscribe({ - next: fetchStub, - }); - - filterManager.removeFilter(readyFilters[0]); - - // this time, events should be emitted - expect(fetchStub).toBeCalledTimes(1); - expect(updateStub).toBeCalledTimes(1); - }); - - test('should remove matching filters', async function() { - filterManager.addFilters([readyFilters[0], readyFilters[1]], true); - filterManager.addFilters([readyFilters[2]], false); - - filterManager.removeFilter(readyFilters[0]); - - expect(globalStateStub.filters).toHaveLength(1); - expect(appStateStub.filters).toHaveLength(1); - }); - - test('should remove matching filters by comparison', async function() { - filterManager.addFilters([readyFilters[0], readyFilters[1]], true); - filterManager.addFilters([readyFilters[2]], false); - - filterManager.removeFilter(_.cloneDeep(readyFilters[0])); - - expect(globalStateStub.filters).toHaveLength(1); - expect(appStateStub.filters).toHaveLength(1); - - filterManager.removeFilter(_.cloneDeep(readyFilters[2])); - expect(globalStateStub.filters).toHaveLength(1); - expect(appStateStub.filters).toHaveLength(0); - }); - - test('should do nothing with a non-matching filter', async function() { - filterManager.addFilters([readyFilters[0], readyFilters[1]], true); - filterManager.addFilters([readyFilters[2]], false); - - const missedFilter = _.cloneDeep(readyFilters[0]); - missedFilter.meta.negate = !readyFilters[0].meta.negate; - - filterManager.removeFilter(missedFilter); - expect(globalStateStub.filters).toHaveLength(2); - expect(appStateStub.filters).toHaveLength(1); - }); - - test('should remove all the filters from both states', async function() { - filterManager.addFilters([readyFilters[0], readyFilters[1]], true); - filterManager.addFilters([readyFilters[2]], false); - expect(globalStateStub.filters).toHaveLength(2); - expect(appStateStub.filters).toHaveLength(1); - - filterManager.removeAll(); - expect(globalStateStub.filters).toHaveLength(0); - expect(appStateStub.filters).toHaveLength(0); - }); - }); - - describe('invert', () => { - test('should fire the update and fetch events', async function() { - filterManager.addFilters(readyFilters); - expect(filterManager.getFilters()).toHaveLength(3); - - const updateStub = jest.fn(); - const fetchStub = jest.fn(); - filterManager.getUpdates$().subscribe({ - next: updateStub, - }); - - filterManager.getFetches$().subscribe({ - next: fetchStub, - }); - - readyFilters[1].meta.negate = !readyFilters[1].meta.negate; - filterManager.addFilters(readyFilters[1]); - expect(filterManager.getFilters()).toHaveLength(3); - expect(fetchStub).toBeCalledTimes(1); - expect(updateStub).toBeCalledTimes(1); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts index d1cad9a8123999..08d5955d3fae9c 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts @@ -19,12 +19,11 @@ import sinon from 'sinon'; -import { FilterStateStore } from '@kbn/es-query'; import { FilterStateManager } from './filter_state_manager'; import { StubState } from './test_helpers/stub_state'; import { getFilter } from './test_helpers/get_stub_filter'; -import { FilterManager } from './filter_manager'; +import { FilterManager, esFilters } from '../../../../../../plugins/data/public'; import { coreMock } from '../../../../../../core/public/mocks'; const setupMock = coreMock.createSetup(); @@ -59,7 +58,7 @@ describe('filter_state_manager', () => { }); test('should NOT watch state until both app and global state are defined', done => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); globalStateStub.filters.push(f1); setTimeout(() => { @@ -72,8 +71,8 @@ describe('filter_state_manager', () => { appStateStub.save = sinon.stub(); globalStateStub.save = sinon.stub(); - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); filterManager.setFilters([f1, f2]); @@ -101,33 +100,37 @@ describe('filter_state_manager', () => { }); test('should update filter manager global filters', done => { - const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - globalStateStub.filters.push(f1); - - setTimeout(() => { + const updateSubscription = filterManager.getUpdates$().subscribe(() => { expect(filterManager.getGlobalFilters()).toHaveLength(1); + if (updateSubscription) { + updateSubscription.unsubscribe(); + } done(); - }, 100); - }); - - test('should update filter manager app filters', done => { - expect(filterManager.getAppFilters()).toHaveLength(0); + }); - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - appStateStub.filters.push(f1); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, true, true, 'age', 34); + globalStateStub.filters.push(f1); + }); - setTimeout(() => { + test('should update filter manager app filter', done => { + const updateSubscription = filterManager.getUpdates$().subscribe(() => { expect(filterManager.getAppFilters()).toHaveLength(1); + if (updateSubscription) { + updateSubscription.unsubscribe(); + } done(); - }, 100); + }); + + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + appStateStub.filters.push(f1); }); test('should update URL when filter manager filters are set', () => { appStateStub.save = sinon.stub(); globalStateStub.save = sinon.stub(); - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); filterManager.setFilters([f1, f2]); @@ -139,8 +142,8 @@ describe('filter_state_manager', () => { appStateStub.save = sinon.stub(); globalStateStub.save = sinon.stub(); - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); filterManager.addFilters([f1, f2]); @@ -156,7 +159,7 @@ describe('filter_state_manager', () => { ** And triggers *another* filter manager update. */ test('should NOT re-trigger filter manager', done => { - const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34); + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); filterManager.setFilters([f1]); const setFiltersSpy = sinon.spy(filterManager, 'setFilters'); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts index 06f91e35db96e8..61821b7ad45e94 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts @@ -17,11 +17,9 @@ * under the License. */ -import { FilterStateStore } from '@kbn/es-query'; - import _ from 'lodash'; import { State } from 'ui/state_management/state'; -import { FilterManager } from './filter_manager'; +import { FilterManager, esFilters } from '../../../../../../plugins/data/public'; type GetAppStateFunc = () => State | undefined | null; @@ -73,8 +71,8 @@ export class FilterStateManager { const newGlobalFilters = _.cloneDeep(globalFilters); const newAppFilters = _.cloneDeep(appFilters); - FilterManager.setFiltersStore(newAppFilters, FilterStateStore.APP_STATE); - FilterManager.setFiltersStore(newGlobalFilters, FilterStateStore.GLOBAL_STATE); + FilterManager.setFiltersStore(newAppFilters, esFilters.FilterStateStore.APP_STATE); + FilterManager.setFiltersStore(newGlobalFilters, esFilters.FilterStateStore.GLOBAL_STATE); this.filterManager.setFilters(newGlobalFilters.concat(newAppFilters)); }, 10); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts index ac533eaaf89ea1..ebb622783c3d1c 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts @@ -17,10 +17,4 @@ * under the License. */ -export { FilterManager } from './filter_manager'; export { FilterStateManager } from './filter_state_manager'; - -export { uniqFilters } from './lib/uniq_filters'; -export { extractTimeFilter } from './lib/extract_time_filter'; -export { changeTimeFilter } from './lib/change_time_filter'; -export { onlyDisabledFiltersChanged } from './lib/only_disabled'; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts deleted file mode 100644 index e8244feb988b6e..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts +++ /dev/null @@ -1,73 +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 { buildQueryFilter, buildEmptyFilter, FilterStateStore } from '@kbn/es-query'; -import { compareFilters } from './compare_filters'; - -describe('filter manager utilities', () => { - describe('compare filters', () => { - test('should compare filters', () => { - const f1 = buildQueryFilter( - { _type: { match: { query: 'apache', type: 'phrase' } } }, - 'index' - ); - const f2 = buildEmptyFilter(true); - - expect(compareFilters(f1, f2)).toBeFalsy(); - }); - - test('should compare duplicates', () => { - const f1 = buildQueryFilter( - { _type: { match: { query: 'apache', type: 'phrase' } } }, - 'index' - ); - const f2 = buildQueryFilter( - { _type: { match: { query: 'apache', type: 'phrase' } } }, - 'index' - ); - - expect(compareFilters(f1, f2)).toBeTruthy(); - }); - - test('should compare duplicates, ignoring meta attributes', () => { - const f1 = buildQueryFilter( - { _type: { match: { query: 'apache', type: 'phrase' } } }, - 'index1' - ); - const f2 = buildQueryFilter( - { _type: { match: { query: 'apache', type: 'phrase' } } }, - 'index2' - ); - - expect(compareFilters(f1, f2)).toBeTruthy(); - }); - - test('should compare duplicates, ignoring $state attributes', () => { - const f1 = { - $state: { store: FilterStateStore.APP_STATE }, - ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), - }; - const f2 = { - $state: { store: FilterStateStore.GLOBAL_STATE }, - ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), - }; - - expect(compareFilters(f1, f2)).toBeTruthy(); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts deleted file mode 100644 index 75bd9d5dfbd81a..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts +++ /dev/null @@ -1,79 +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 { Filter, buildRangeFilter, FilterStateStore, buildQueryFilter } from '@kbn/es-query'; -import { dedupFilters } from './dedup_filters'; - -describe('filter manager utilities', () => { - describe('dedupFilters(existing, filters)', () => { - test('should return only filters which are not in the existing', () => { - const existing: Filter[] = [ - buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'), - buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), - ]; - const filters: Filter[] = [ - buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'), - buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), - ]; - const results = dedupFilters(existing, filters); - - expect(results).toContain(filters[0]); - expect(results).not.toContain(filters[1]); - }); - - test('should ignore the disabled attribute when comparing ', () => { - const existing: Filter[] = [ - buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'), - { - ...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), - meta: { disabled: true, negate: false, alias: null }, - }, - ]; - const filters: Filter[] = [ - buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'), - buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), - ]; - const results = dedupFilters(existing, filters); - - expect(results).toContain(filters[0]); - expect(results).not.toContain(filters[1]); - }); - - test('should ignore $state attribute', () => { - const existing: Filter[] = [ - buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'), - { - ...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), - $state: { store: FilterStateStore.APP_STATE }, - }, - ]; - const filters: Filter[] = [ - buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'), - { - ...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), - $state: { store: FilterStateStore.GLOBAL_STATE }, - }, - ]; - const results = dedupFilters(existing, filters); - - expect(results).toContain(filters[0]); - expect(results).not.toContain(filters[1]); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts deleted file mode 100644 index d55c9babeed796..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts +++ /dev/null @@ -1,58 +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 { Filter, buildRangeFilter, buildQueryFilter, buildPhraseFilter } from '@kbn/es-query'; -import { extractTimeFilter } from './extract_time_filter'; - -describe('filter manager utilities', () => { - describe('extractTimeFilter()', () => { - test('should detect timeFilter', async () => { - const filters: Filter[] = [ - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'), - buildRangeFilter({ name: 'time' }, { gt: 1388559600000, lt: 1388646000000 }, 'logstash-*'), - ]; - const result = await extractTimeFilter('time', filters); - - expect(result.timeRangeFilter).toEqual(filters[1]); - expect(result.restOfFilters[0]).toEqual(filters[0]); - }); - - test("should not return timeFilter when name doesn't match", async () => { - const filters: Filter[] = [ - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'), - buildRangeFilter({ name: '@timestamp' }, { from: 1, to: 2 }, 'logstash-*'), - ]; - const result = await extractTimeFilter('time', filters); - - expect(result.timeRangeFilter).toBeUndefined(); - expect(result.restOfFilters).toEqual(filters); - }); - - test('should not return a non range filter, even when names match', async () => { - const filters: Filter[] = [ - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'), - buildPhraseFilter({ name: 'time' }, 'banana', 'logstash-*'), - ]; - const result = await extractTimeFilter('time', filters); - - expect(result.timeRangeFilter).toBeUndefined(); - expect(result.restOfFilters).toEqual(filters); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts deleted file mode 100644 index efa348c9ad3206..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts +++ /dev/null @@ -1,65 +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 { get } from 'lodash'; -import { - PhraseFilter, - Filter, - FILTERS, - isPhraseFilter, - isScriptedPhraseFilter, - getPhraseFilterField, - getPhraseFilterValue, - FilterValueFormatter, -} from '@kbn/es-query'; - -const getScriptedPhraseValue = (filter: PhraseFilter) => - get(filter, ['script', 'script', 'params', 'value']); - -const getFormattedValueFn = (value: any) => { - return (formatter?: FilterValueFormatter) => { - return formatter ? formatter.convert(value) : value; - }; -}; - -const getParams = (filter: PhraseFilter) => { - const scriptedPhraseValue = getScriptedPhraseValue(filter); - const isScriptedFilter = Boolean(scriptedPhraseValue); - const key = isScriptedFilter ? filter.meta.field || '' : getPhraseFilterField(filter); - const query = scriptedPhraseValue || getPhraseFilterValue(filter); - const params = { query }; - - return { - key, - params, - type: FILTERS.PHRASE, - value: getFormattedValueFn(query), - }; -}; - -export const isMapPhraseFilter = (filter: any): filter is PhraseFilter => - isPhraseFilter(filter) || isScriptedPhraseFilter(filter); - -export const mapPhrase = (filter: Filter) => { - if (!isMapPhraseFilter(filter)) { - throw filter; - } - - return getParams(filter); -}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts deleted file mode 100644 index 12d2919e2d47b6..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts +++ /dev/null @@ -1,70 +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 { mapRange } from './map_range'; -import { RangeFilter, Filter, FilterMeta } from '@kbn/es-query'; - -describe('filter manager utilities', () => { - describe('mapRange()', () => { - test('should return the key and value for matching filters with gt/lt', async () => { - const filter = { - meta: { index: 'logstash-*' } as FilterMeta, - range: { bytes: { lt: 2048, gt: 1024 } }, - } as RangeFilter; - const result = mapRange(filter); - - expect(result).toHaveProperty('key', 'bytes'); - expect(result).toHaveProperty('value'); - if (result.value) { - const displayName = result.value(); - expect(displayName).toBe('1024 to 2048'); - } - }); - - test('should return the key and value for matching filters with gte/lte', async () => { - const filter = { - meta: { index: 'logstash-*' } as FilterMeta, - range: { bytes: { lte: 2048, gte: 1024 } }, - } as RangeFilter; - const result = mapRange(filter); - - expect(result).toHaveProperty('key', 'bytes'); - expect(result).toHaveProperty('value'); - if (result.value) { - const displayName = result.value(); - expect(displayName).toBe('1024 to 2048'); - } - }); - - test('should return undefined for none matching', async done => { - const filter = { - meta: { index: 'logstash-*' }, - query: { query_string: { query: 'foo:bar' } }, - } as Filter; - - try { - mapRange(filter); - } catch (e) { - expect(e).toBe(filter); - - done(); - } - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts deleted file mode 100644 index 76f9d3621e1717..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts +++ /dev/null @@ -1,71 +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 { - Filter, - RangeFilter, - FILTERS, - isRangeFilter, - isScriptedRangeFilter, - FilterValueFormatter, -} from '@kbn/es-query'; -import { get, has } from 'lodash'; - -const getFormattedValueFn = (left: any, right: any) => { - return (formatter?: FilterValueFormatter) => { - let displayValue = `${left} to ${right}`; - if (formatter) { - const convert = formatter.getConverterFor('text'); - displayValue = `${convert(left)} to ${convert(right)}`; - } - return displayValue; - }; -}; - -const getFirstRangeKey = (filter: RangeFilter) => filter.range && Object.keys(filter.range)[0]; -const getRangeByKey = (filter: RangeFilter, key: string) => get(filter, ['range', key]); - -function getParams(filter: RangeFilter) { - const isScriptedRange = isScriptedRangeFilter(filter); - const key: string = (isScriptedRange ? filter.meta.field : getFirstRangeKey(filter)) || ''; - const params: any = isScriptedRange - ? get(filter, 'script.script.params') - : getRangeByKey(filter, key); - - let left = has(params, 'gte') ? params.gte : params.gt; - if (left == null) left = -Infinity; - - let right = has(params, 'lte') ? params.lte : params.lt; - if (right == null) right = Infinity; - - const value = getFormattedValueFn(left, right); - - return { type: FILTERS.RANGE, key, value, params }; -} - -export const isMapRangeFilter = (filter: any): filter is RangeFilter => - isRangeFilter(filter) || isScriptedRangeFilter(filter); - -export const mapRange = (filter: Filter) => { - if (!isMapRangeFilter(filter)) { - throw filter; - } - - return getParams(filter); -}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts deleted file mode 100644 index 86f059913cd96a..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts +++ /dev/null @@ -1,60 +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 { Filter, buildQueryFilter, FilterStateStore } from '@kbn/es-query'; -import { uniqFilters } from './uniq_filters'; - -describe('filter manager utilities', () => { - describe('niqFilter', () => { - test('should filter out dups', () => { - const before: Filter[] = [ - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), - ]; - const results = uniqFilters(before); - - expect(results).toHaveLength(1); - }); - - test('should filter out duplicates, ignoring meta attributes', () => { - const before: Filter[] = [ - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index1'), - buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index2'), - ]; - const results = uniqFilters(before); - - expect(results).toHaveLength(1); - }); - - test('should filter out duplicates, ignoring $state attributes', () => { - const before: Filter[] = [ - { - $state: { store: FilterStateStore.APP_STATE }, - ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), - }, - { - $state: { store: FilterStateStore.GLOBAL_STATE }, - ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), - }, - ]; - const results = uniqFilters(before); - - expect(results).toHaveLength(1); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/partitioned_filters.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/partitioned_filters.ts deleted file mode 100644 index e74b48b722cc45..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/partitioned_filters.ts +++ /dev/null @@ -1,25 +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 { Filter } from '@kbn/es-query'; - -export interface PartitionedFilters { - globalFilters: Filter[]; - appFilters: Filter[]; -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts index 20d9e236f49be8..5238efe5efa59c 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts @@ -17,15 +17,15 @@ * under the License. */ -import { Filter, FilterStateStore } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../plugins/data/public'; export function getFilter( - store: FilterStateStore, + store: esFilters.FilterStateStore, disabled: boolean, negated: boolean, queryKey: string, queryValue: any -): Filter { +): esFilters.Filter { return { $state: { store, diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts deleted file mode 100644 index d429fc7f70f387..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts +++ /dev/null @@ -1,28 +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. - */ - -export class StubIndexPatterns { - async get(index: string) { - return { - fields: { - getByName: () => undefined, - }, - }; - } -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts index ab92016d1b9ab3..f0a4bdef0229d0 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts @@ -19,11 +19,11 @@ import sinon from 'sinon'; -import { Filter } from '@kbn/es-query'; import { State } from 'ui/state_management/state'; +import { esFilters } from '../../../../../../../plugins/data/public'; export class StubState implements State { - filters: Filter[]; + filters: esFilters.Filter[]; save: sinon.SinonSpy; constructor() { diff --git a/src/legacy/core_plugins/data/public/filter/filter_service.mock.ts b/src/legacy/core_plugins/data/public/filter/filter_service.mock.ts deleted file mode 100644 index 94268ef69c49a8..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_service.mock.ts +++ /dev/null @@ -1,56 +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 { FilterService, FilterStart, FilterSetup } from '.'; - -type FilterServiceClientContract = PublicMethodsOf; - -const createSetupContractMock = () => { - const setupContract: jest.Mocked = { - filterManager: jest.fn() as any, - }; - - return setupContract; -}; - -const createStartContractMock = () => { - const startContract: jest.Mocked = { - filterManager: jest.fn() as any, - }; - - return startContract; -}; - -const createMock = () => { - const mocked: jest.Mocked = { - setup: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - }; - - mocked.setup.mockReturnValue(createSetupContractMock()); - mocked.start.mockReturnValue(createStartContractMock()); - return mocked; -}; - -export const filterServiceMock = { - create: createMock, - createSetupContract: createSetupContractMock, - createStartContract: createStartContractMock, -}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_service.ts b/src/legacy/core_plugins/data/public/filter/filter_service.ts deleted file mode 100644 index 0c46259ef0e00a..00000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_service.ts +++ /dev/null @@ -1,56 +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 { UiSettingsClientContract } from 'src/core/public'; -import { FilterManager } from './filter_manager'; - -/** - * Filter Service - * @internal - */ - -export interface FilterServiceDependencies { - uiSettings: UiSettingsClientContract; -} - -export class FilterService { - filterManager!: FilterManager; - - public setup({ uiSettings }: FilterServiceDependencies) { - this.filterManager = new FilterManager(uiSettings); - - return { - filterManager: this.filterManager, - }; - } - - public start() { - return { - filterManager: this.filterManager, - }; - } - - public stop() { - // nothing to do here yet - } -} - -/** @public */ -export type FilterSetup = ReturnType; -export type FilterStart = ReturnType; diff --git a/src/legacy/core_plugins/data/public/filter/index.tsx b/src/legacy/core_plugins/data/public/filter/index.tsx index cda7350ecadef1..005c4904a4f392 100644 --- a/src/legacy/core_plugins/data/public/filter/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/index.tsx @@ -17,8 +17,6 @@ * under the License. */ -export * from './filter_service'; - export { FilterBar } from './filter_bar'; export { ApplyFiltersPopover } from './apply_filters'; diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index cb3869ff57711f..60828b4a2a2025 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -43,14 +43,7 @@ export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './s /** @public static code */ export * from '../common'; -export { - FilterManager, - FilterStateManager, - uniqFilters, - extractTimeFilter, - changeTimeFilter, - onlyDisabledFiltersChanged, -} from './filter/filter_manager'; +export { FilterStateManager } from './filter/filter_manager'; export { CONTAINS_SPACES, getFromSavedObject, @@ -68,5 +61,3 @@ export { mockFields, mockIndexPattern, } from './index_patterns'; - -export { TimeHistoryContract, TimefilterContract, getTime, InputTimeRange } from './timefilter'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts index 628b1999b1c231..dc5023795bf19b 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts @@ -19,7 +19,6 @@ // @ts-ignore import { fieldFormats } from 'ui/registry/field_formats'; -import { NotificationsSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; @@ -27,9 +26,15 @@ import { FieldFormat } from '../../../../../../plugins/data/common/field_formats // @ts-ignore import { shortenDottedString } from '../../../../../core_plugins/kibana/common/utils/shorten_dotted_string'; import { IndexPattern } from '../index_patterns'; +import { getNotifications } from '../services'; import { getKbnFieldType } from '../../../../../../plugins/data/public'; +interface FieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} + export type FieldSpec = Record; export interface FieldType { name: string; @@ -47,8 +52,7 @@ export interface FieldType { visualizable?: boolean; readFromDocValues?: boolean; scripted?: boolean; - parent?: string; - subType?: string; + subType?: FieldSubType; displayName?: string; format?: any; } @@ -68,8 +72,7 @@ export class Field implements FieldType { sortable?: boolean; visualizable?: boolean; scripted?: boolean; - parent?: string; - subType?: string; + subType?: FieldSubType; displayName?: string; format: any; routes: Record = { @@ -80,8 +83,7 @@ export class Field implements FieldType { constructor( indexPattern: IndexPattern, spec: FieldSpec | Field, - shortDotsEnable: boolean = false, - notifications: NotificationsSetup + shortDotsEnable: boolean = false ) { // unwrap old instances of Field if (spec instanceof Field) spec = spec.$$spec; @@ -106,8 +108,9 @@ export class Field implements FieldType { values: { name: spec.name, title: indexPattern.title }, defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', }); + const { toasts } = getNotifications(); - notifications.toasts.addDanger({ + toasts.addDanger({ title, text, }); @@ -165,7 +168,6 @@ export class Field implements FieldType { obj.writ('conflictDescriptions'); // multi info - obj.fact('parent'); obj.fact('subType'); return obj.create(); diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts b/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts index 30f4df66f38636..108aacc8e07def 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts @@ -17,7 +17,6 @@ * under the License. */ -import { NotificationsSetup } from 'kibana/public'; import { findIndex } from 'lodash'; import { IndexPattern } from '../index_patterns'; import { Field, FieldType, FieldSpec } from './field'; @@ -36,7 +35,6 @@ export class FieldList extends Array implements FieldListInterface { private groups: Map = new Map(); private indexPattern: IndexPattern; private shortDotsEnable: boolean; - private notifications: NotificationsSetup; private setByName = (field: Field) => this.byName.set(field.name, field); private setByGroup = (field: Field) => { if (typeof this.groups.get(field.type) === 'undefined') { @@ -45,23 +43,19 @@ export class FieldList extends Array implements FieldListInterface { this.groups.get(field.type)!.set(field.name, field); }; private removeByGroup = (field: FieldType) => this.groups.get(field.type)!.delete(field.name); - constructor( - indexPattern: IndexPattern, - specs: FieldSpec[] = [], - shortDotsEnable = false, - notifications: NotificationsSetup - ) { + + constructor(indexPattern: IndexPattern, specs: FieldSpec[] = [], shortDotsEnable = false) { super(); this.indexPattern = indexPattern; this.shortDotsEnable = shortDotsEnable; - this.notifications = notifications; + specs.map(field => this.add(field)); } getByName = (name: Field['name']) => this.byName.get(name); getByType = (type: Field['type']) => [...(this.groups.get(type) || new Map()).values()]; add = (field: FieldSpec) => { - const newField = new Field(this.indexPattern, field, this.shortDotsEnable, this.notifications); + const newField = new Field(this.indexPattern, field, this.shortDotsEnable); this.push(newField); this.setByName(newField); this.setByGroup(newField); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts index adc34e34f58e56..a3653bb529fa38 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts @@ -21,7 +21,7 @@ import { IndexPattern } from './index_pattern'; export interface PatternCache { get: (id: string) => IndexPattern; - set: (id: string, value: Promise) => Promise; + set: (id: string, value: IndexPattern) => IndexPattern; clear: (id: string) => void; clearAll: () => void; } diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts index 1ab229f802ab26..18c6578e3142de 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { IndexPattern } from './'; +import { IndexPattern } from './index_pattern'; // Takes a hit, merges it with any stored/scripted fields, and with the metaFields // returns a flattened version diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index d8bd9623f5c088..2d43faf49f63db 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -27,6 +27,11 @@ import mockLogStashFields from '../../../../../../fixtures/logstash_fields'; import { stubbedSavedObjectIndexPattern } from '../../../../../../fixtures/stubbed_saved_object_index_pattern'; import { Field } from '../index_patterns_service'; +import { setNotifications } from '../services'; + +// Temporary disable eslint, will be removed after moving to new platform folder +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { notificationServiceMock } from '../../../../../../core/public/notifications/notifications_service.mock'; jest.mock('ui/registry/field_formats', () => ({ fieldFormats: { @@ -109,18 +114,6 @@ const apiClient = { getFieldsForWildcard: jest.fn(), }; -const notifications = { - toasts: { - addDanger: jest.fn(), - addError: jest.fn(), - add: jest.fn(), - addWarning: jest.fn(), - addSuccess: jest.fn(), - remove: jest.fn(), - get$: jest.fn(), - }, -}; - // helper function to create index patterns function create(id: string, payload?: any): Promise { const indexPattern = new IndexPattern( @@ -128,8 +121,7 @@ function create(id: string, payload?: any): Promise { (cfg: any) => config.get(cfg), savedObjectsClient as any, apiClient, - patternCache, - notifications + patternCache ); setDocsourcePayload(id, payload); @@ -143,11 +135,14 @@ function setDocsourcePayload(id: string | null, providedPayload: any) { describe('IndexPattern', () => { const indexPatternId = 'test-pattern'; + const notifications = notificationServiceMock.createStartContract(); let indexPattern: IndexPattern; // create an indexPattern instance for each test beforeEach(() => { + setNotifications(notifications); + return create(indexPatternId).then((pattern: IndexPattern) => { indexPattern = pattern; }); @@ -392,8 +387,7 @@ describe('IndexPattern', () => { (cfg: any) => config.get(cfg), savedObjectsClient as any, apiClient, - patternCache, - notifications + patternCache ); await pattern.init(); @@ -405,8 +399,7 @@ describe('IndexPattern', () => { (cfg: any) => config.get(cfg), savedObjectsClient as any, apiClient, - patternCache, - notifications + patternCache ); await samePattern.init(); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 0a1eb4c36ae7bf..bf0d79e960d9b8 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -21,7 +21,7 @@ import _, { each, reject } from 'lodash'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { fieldFormats } from 'ui/registry/field_formats'; -import { NotificationsSetup, SavedObjectsClientContract } from 'src/core/public'; +import { SavedObjectsClientContract } from 'src/core/public'; import { DuplicateField, SavedObjectNotFound, @@ -38,6 +38,7 @@ import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { getNotifications } from '../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; @@ -64,7 +65,6 @@ export class IndexPattern implements StaticIndexPattern { public formatField: any; public flattenHit: any; public metaFields: string[]; - public notifications: NotificationsSetup; private version: string | undefined; private savedObjectsClient: SavedObjectsClientContract; @@ -102,8 +102,7 @@ export class IndexPattern implements StaticIndexPattern { getConfig: any, savedObjectsClient: SavedObjectsClientContract, apiClient: IIndexPatternsApiClient, - patternCache: any, - notifications: NotificationsSetup + patternCache: any ) { this.id = id; this.savedObjectsClient = savedObjectsClient; @@ -111,12 +110,11 @@ export class IndexPattern implements StaticIndexPattern { // instead of storing config we rather store the getter only as np uiSettingsClient has circular references // which cause problems when being consumed from angular this.getConfig = getConfig; - this.notifications = notifications; this.shortDotsEnable = this.getConfig('shortDots:enable'); this.metaFields = this.getConfig('metaFields'); - this.fields = new FieldList(this, [], this.shortDotsEnable, notifications); + this.fields = new FieldList(this, [], this.shortDotsEnable); this.fieldsFetcher = createFieldsFetcher(this, apiClient, this.getConfig('metaFields')); this.flattenHit = flattenHitWrapper(this, this.getConfig('metaFields')); this.formatHit = formatHitProvider(this, fieldFormats.getDefaultInstance('string')); @@ -136,7 +134,7 @@ export class IndexPattern implements StaticIndexPattern { private initFields(input?: any) { const newValue = input || this.fields; - this.fields = new FieldList(this, newValue, this.shortDotsEnable, this.notifications); + this.fields = new FieldList(this, newValue, this.shortDotsEnable); } private isFieldRefreshRequired(): boolean { @@ -286,8 +284,7 @@ export class IndexPattern implements StaticIndexPattern { filterable: true, searchable: true, }, - false, - this.notifications + false ) ); @@ -370,8 +367,7 @@ export class IndexPattern implements StaticIndexPattern { this.getConfig, this.savedObjectsClient, this.patternCache, - this.fieldsFetcher, - this.notifications + this.fieldsFetcher ); await duplicatePattern.destroy(); } @@ -423,8 +419,7 @@ export class IndexPattern implements StaticIndexPattern { this.getConfig, this.savedObjectsClient, this.patternCache, - this.fieldsFetcher, - this.notifications + this.fieldsFetcher ); return samePattern.init().then(() => { // What keys changed from now and what the server returned @@ -456,7 +451,9 @@ export class IndexPattern implements StaticIndexPattern { 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', } // eslint-disable-line max-len ); - this.notifications.toasts.addDanger(message); + const { toasts } = getNotifications(); + + toasts.addDanger(message); throw err; } @@ -494,12 +491,14 @@ export class IndexPattern implements StaticIndexPattern { // we still want to notify the user that there is a problem // but we do not want to potentially make any pages unusable // so do not rethrow the error here + const { toasts } = getNotifications(); + if (err instanceof IndexPatternMissingIndices) { - this.notifications.toasts.addDanger((err as any).message); + toasts.addDanger((err as any).message); return []; } - this.notifications.toasts.addError(err, { + toasts.addError(err, { title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { defaultMessage: 'Error fetching fields', }), diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index f573f33ef7a527..8a5c78d13c251c 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -23,7 +23,6 @@ import { SavedObjectsClientContract, UiSettingsClientContract, HttpServiceBase, - NotificationsSetup, } from 'kibana/public'; jest.mock('../errors', () => ({ @@ -65,14 +64,15 @@ describe('IndexPatterns', () => { const savedObjectsClient = {} as SavedObjectsClientContract; const uiSettings = {} as UiSettingsClientContract; const http = {} as HttpServiceBase; - const notifications = {} as NotificationsSetup; - indexPatterns = new IndexPatterns(uiSettings, savedObjectsClient, http, notifications); + indexPatterns = new IndexPatterns(uiSettings, savedObjectsClient, http); }); - test('does cache gets for the same id', () => { + test('does cache gets for the same id', async () => { const id = '1'; + const indexPattern = await indexPatterns.get(id); - expect(indexPatterns.get(id)).toBe(indexPatterns.get(id)); + expect(indexPattern).toBeDefined(); + expect(indexPattern).toBe(await indexPatterns.get(id)); }); }); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts index fc5ef6cb75355a..4767b6d3a3ca7a 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -23,14 +23,13 @@ import { SimpleSavedObject, UiSettingsClientContract, HttpServiceBase, - NotificationsSetup, } from 'src/core/public'; // @ts-ignore import { fieldFormats } from 'ui/registry/field_formats'; import { createIndexPatternCache } from './_pattern_cache'; import { IndexPattern } from './index_pattern'; -import { IndexPatternsApiClient } from './index_patterns_api_client'; +import { IndexPatternsApiClient, GetFieldsOptions } from './index_patterns_api_client'; const indexPatternCache = createIndexPatternCache(); @@ -41,17 +40,13 @@ export class IndexPatterns { private savedObjectsClient: SavedObjectsClientContract; private savedObjectsCache?: Array>> | null; private apiClient: IndexPatternsApiClient; - private notifications: NotificationsSetup; constructor( config: UiSettingsClientContract, savedObjectsClient: SavedObjectsClientContract, - http: HttpServiceBase, - notifications: NotificationsSetup + http: HttpServiceBase ) { this.apiClient = new IndexPatternsApiClient(http); - this.notifications = notifications; - this.config = config; this.savedObjectsClient = savedObjectsClient; } @@ -98,6 +93,14 @@ export class IndexPatterns { }); }; + getFieldsForTimePattern = (options: GetFieldsOptions = {}) => { + return this.apiClient.getFieldsForTimePattern(options); + }; + + getFieldsForWildcard = (options: GetFieldsOptions = {}) => { + return this.apiClient.getFieldsForWildcard(options); + }; + clearCache = (id?: string) => { this.savedObjectsCache = null; if (id) { @@ -122,19 +125,26 @@ export class IndexPatterns { return null; }; - get = (id: string) => { + get = async (id: string): Promise => { const cache = indexPatternCache.get(id); - return cache || indexPatternCache.set(id, this.make(id)); + if (cache) { + return cache; + } + + const indexPattern = await this.make(id); + + return indexPatternCache.set(id, indexPattern); }; make = (id?: string): Promise => { - return new IndexPattern( + const indexPattern = new IndexPattern( id, (cfg: any) => this.config.get(cfg), this.savedObjectsClient, this.apiClient, - indexPatternCache, - this.notifications - ).init(); + indexPatternCache + ); + + return indexPattern.init(); }; } diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts index 562dcb248edae1..bdeeb787c983d6 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts @@ -21,11 +21,13 @@ import { UiSettingsClientContract, SavedObjectsClientContract, HttpServiceBase, - NotificationsSetup, + NotificationsStart, } from 'src/core/public'; import { Field, FieldList, FieldListInterface, FieldType } from './fields'; import { createFlattenHitWrapper } from './index_patterns'; import { createIndexPatternSelect } from './components'; +import { setNotifications } from './services'; + import { formatHitProvider, IndexPattern, @@ -37,7 +39,7 @@ export interface IndexPatternDependencies { uiSettings: UiSettingsClientContract; savedObjectsClient: SavedObjectsClientContract; http: HttpServiceBase; - notifications: NotificationsSetup; + notifications: NotificationsStart; } /** @@ -63,9 +65,11 @@ export class IndexPatternsService { } public start({ uiSettings, savedObjectsClient, http, notifications }: IndexPatternDependencies) { + setNotifications(notifications); + return { ...this.setupApi, - indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http, notifications), + indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http), IndexPatternSelect: createIndexPatternSelect(savedObjectsClient), }; } diff --git a/src/legacy/core_plugins/data/public/index_patterns/services.ts b/src/legacy/core_plugins/data/public/index_patterns/services.ts new file mode 100644 index 00000000000000..5cc087548d6fb1 --- /dev/null +++ b/src/legacy/core_plugins/data/public/index_patterns/services.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. + */ + +import { NotificationsStart } from 'src/core/public'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; + +export const [getNotifications, setNotifications] = createGetterSetter( + 'Notifications' +); diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts index e151726a6d702d..b1d838aed992da 100644 --- a/src/legacy/core_plugins/data/public/legacy.ts +++ b/src/legacy/core_plugins/data/public/legacy.ts @@ -35,18 +35,13 @@ */ import { npSetup, npStart } from 'ui/new_platform'; -import { LegacyDependenciesPlugin } from './shim/legacy_dependencies_plugin'; import { plugin } from '.'; const dataPlugin = plugin(); -const legacyPlugin = new LegacyDependenciesPlugin(); -export const setup = dataPlugin.setup(npSetup.core, { - __LEGACY: legacyPlugin.setup(), -}); +export const setup = dataPlugin.setup(npSetup.core); export const start = dataPlugin.start(npStart.core, { data: npStart.plugins.data, uiActions: npSetup.plugins.uiActions, - __LEGACY: legacyPlugin.start(), }); diff --git a/src/legacy/core_plugins/data/public/mocks.ts b/src/legacy/core_plugins/data/public/mocks.ts index 2a82927bb3ebfb..d3b5944127965d 100644 --- a/src/legacy/core_plugins/data/public/mocks.ts +++ b/src/legacy/core_plugins/data/public/mocks.ts @@ -17,17 +17,13 @@ * under the License. */ -import { filterServiceMock } from './filter/filter_service.mock'; import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock'; import { queryServiceMock } from './query/query_service.mock'; -import { timefilterServiceMock } from './timefilter/timefilter_service.mock'; function createDataSetupMock() { return { - filter: filterServiceMock.createSetupContract(), indexPatterns: indexPatternsServiceMock.createSetupContract(), query: queryServiceMock.createSetupContract(), - timefilter: timefilterServiceMock.createSetupContract(), }; } diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 5c9d9317d8270c..76beb4ee560535 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -20,14 +20,10 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search'; import { QueryService, QuerySetup } from './query'; -import { FilterService, FilterSetup, FilterStart } from './filter'; -import { TimefilterService, TimefilterSetup } from './timefilter'; import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns'; -import { - LegacyDependenciesPluginSetup, - LegacyDependenciesPluginStart, -} from './shim/legacy_dependencies_plugin'; +import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { initLegacyModule } from './shim/legacy_module'; import { IUiActionsSetup } from '../../../../plugins/ui_actions/public'; import { createFilterAction, @@ -35,19 +31,9 @@ import { } from './filter/action/apply_filter_action'; import { APPLY_FILTER_TRIGGER } from '../../../../plugins/embeddable/public'; -/** - * Interface for any dependencies on other plugins' `setup` contracts. - * - * @internal - */ -export interface DataPluginSetupDependencies { - __LEGACY: LegacyDependenciesPluginSetup; -} - export interface DataPluginStartDependencies { data: DataPublicPluginStart; uiActions: IUiActionsSetup; - __LEGACY: LegacyDependenciesPluginStart; } /** @@ -57,9 +43,7 @@ export interface DataPluginStartDependencies { */ export interface DataSetup { query: QuerySetup; - timefilter: TimefilterSetup; indexPatterns: IndexPatternsSetup; - filter: FilterSetup; } /** @@ -69,9 +53,7 @@ export interface DataSetup { */ export interface DataStart { query: QuerySetup; - timefilter: TimefilterSetup; indexPatterns: IndexPatternsStart; - filter: FilterStart; search: SearchStart; ui: { SearchBar: React.ComponentType; @@ -89,42 +71,27 @@ export interface DataStart { * in the setup/start interfaces. The remaining items exported here are either types, * or static code. */ -export class DataPlugin - implements - Plugin { - // Exposed services, sorted alphabetically - private readonly filter: FilterService = new FilterService(); + +export class DataPlugin implements Plugin { private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); private readonly query: QueryService = new QueryService(); private readonly search: SearchService = new SearchService(); - private readonly timefilter: TimefilterService = new TimefilterService(); private setupApi!: DataSetup; + private storage!: IStorageWrapper; - public setup(core: CoreSetup, { __LEGACY }: DataPluginSetupDependencies): DataSetup { - const { uiSettings } = core; + public setup(core: CoreSetup): DataSetup { + this.storage = new Storage(window.localStorage); - const timefilterService = this.timefilter.setup({ - uiSettings, - store: __LEGACY.storage, - }); - const filterService = this.filter.setup({ - uiSettings, - }); this.setupApi = { indexPatterns: this.indexPatterns.setup(), query: this.query.setup(), - timefilter: timefilterService, - filter: filterService, }; return this.setupApi; } - public start( - core: CoreStart, - { __LEGACY, data, uiActions }: DataPluginStartDependencies - ): DataStart { + public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { const { uiSettings, http, notifications, savedObjects } = core; const indexPatternsService = this.indexPatterns.start({ @@ -134,18 +101,19 @@ export class DataPlugin notifications, }); + initLegacyModule(indexPatternsService.indexPatterns); + const SearchBar = createSearchBar({ core, data, - store: __LEGACY.storage, - timefilter: this.setupApi.timefilter, - filterManager: this.setupApi.filter.filterManager, + storage: this.storage, }); uiActions.registerAction( createFilterAction( - this.setupApi.filter.filterManager, - this.setupApi.timefilter.timefilter, + core.overlays, + data.query.filterManager, + data.query.timefilter.timefilter, indexPatternsService ) ); @@ -164,9 +132,7 @@ export class DataPlugin public stop() { this.indexPatterns.stop(); - this.filter.stop(); this.query.stop(); this.search.stop(); - this.timefilter.stop(); } } diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap index 06f9e6081e5222..5dc8702411783d 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap @@ -103,6 +103,13 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -229,10 +236,20 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -287,12 +304,12 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto "update": [MockFunction], }, }, - "store": Object { + "storage": Object { "clear": [MockFunction], "get": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "store": Object { + "storage": Object { "clear": [MockFunction], "getItem": [MockFunction], "key": [MockFunction], @@ -332,7 +349,7 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto } } > - - - - + + @@ -1183,6 +1217,13 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -1309,10 +1350,20 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -1367,12 +1418,12 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 "update": [MockFunction], }, }, - "store": Object { + "storage": Object { "clear": [MockFunction], "get": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "store": Object { + "storage": Object { "clear": [MockFunction], "getItem": [MockFunction], "key": [MockFunction], @@ -1412,7 +1463,7 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 } } > - - - - + + @@ -2260,6 +2328,13 @@ exports[`QueryBarInput Should render the given query 1`] = ` }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -2386,10 +2461,20 @@ exports[`QueryBarInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -2444,12 +2529,12 @@ exports[`QueryBarInput Should render the given query 1`] = ` "update": [MockFunction], }, }, - "store": Object { + "storage": Object { "clear": [MockFunction], "get": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "store": Object { + "storage": Object { "clear": [MockFunction], "getItem": [MockFunction], "key": [MockFunction], @@ -2489,7 +2574,7 @@ exports[`QueryBarInput Should render the given query 1`] = ` } } > - - - - + + diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts index 80ee38ea1b076e..d0abed8e862641 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.mocks.ts @@ -45,7 +45,7 @@ export const mockFetchIndexPatterns = jest .fn() .mockReturnValue(Promise.resolve([mockIndexPattern])); -jest.mock('../../persisted_log', () => ({ +jest.mock('../../../../../../../plugins/data/public/query/persisted_log', () => ({ PersistedLog: mockPersistedLogFactory, })); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx index f1249da997dfe9..3edb689ca2bfeb 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx @@ -58,7 +58,7 @@ const createMockWebStorage = () => ({ }); const createMockStorage = () => ({ - store: createMockWebStorage(), + storage: createMockWebStorage(), get: jest.fn(), set: jest.fn(), remove: jest.fn(), @@ -80,7 +80,7 @@ const mockIndexPattern = { ], } as IndexPattern; -function wrapQueryBarInputInContext(testProps: any, store?: any) { +function wrapQueryBarInputInContext(testProps: any, storage?: any) { const defaultOptions = { screenTitle: 'Another Screen', intl: null as any, @@ -89,7 +89,7 @@ function wrapQueryBarInputInContext(testProps: any, store?: any) { const services = { ...startMock, appName: testProps.appName || 'test', - store: store || createMockStorage(), + storage: storage || createMockStorage(), }; return ( diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx index a57018b1181850..5576427b1592a6 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx @@ -21,14 +21,23 @@ import { Component } from 'react'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFieldText, EuiOutsideClickDetector, PopoverAnchorPosition } from '@elastic/eui'; - -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { + EuiFieldText, + EuiOutsideClickDetector, + PopoverAnchorPosition, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiLink, +} from '@elastic/eui'; + +import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { debounce, compact, isEqual } from 'lodash'; - +import { Toast } from 'src/core/public'; import { AutocompleteSuggestion, AutocompleteSuggestionType, + PersistedLog, } from '../../../../../../../plugins/data/public'; import { withKibana, @@ -39,7 +48,6 @@ import { Query, getQueryLog } from '../index'; import { fromUser, matchPairs, toUser } from '../lib'; import { QueryLanguageSwitcher } from './language_switcher'; import { SuggestionsComponent } from './typeahead/suggestions_component'; -import { PersistedLog } from '../../persisted_log'; import { fetchIndexPatterns } from '../lib/fetch_index_patterns'; import { IDataPluginServices } from '../../../types'; @@ -302,20 +310,13 @@ export class QueryBarInputUI extends Component { } }; - private selectSuggestion = ({ - type, - text, - start, - end, - }: { - type: AutocompleteSuggestionType; - text: string; - start: number; - end: number; - }) => { + private selectSuggestion = (suggestion: AutocompleteSuggestion) => { if (!this.inputRef) { return; } + const { type, text, start, end, cursorIndex } = suggestion; + + this.handleNestedFieldSyntaxNotification(suggestion); const query = this.getQueryString(); const { selectionStart, selectionEnd } = this.inputRef; @@ -328,12 +329,75 @@ export class QueryBarInputUI extends Component { this.onQueryStringChange(newQueryString); + this.setState({ + selectionStart: start + (cursorIndex ? cursorIndex : text.length), + selectionEnd: start + (cursorIndex ? cursorIndex : text.length), + }); + if (type === recentSearchType) { this.setState({ isSuggestionsVisible: false, index: null }); this.onSubmit({ query: newQueryString, language: this.props.query.language }); } }; + private handleNestedFieldSyntaxNotification = (suggestion: AutocompleteSuggestion) => { + if ( + 'field' in suggestion && + suggestion.field.subType && + suggestion.field.subType.nested && + !this.services.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') + ) { + const { notifications, docLinks } = this.services; + + const onKQLNestedQuerySyntaxInfoOptOut = (toast: Toast) => { + if (!this.services.storage) return; + this.services.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); + notifications!.toasts.remove(toast); + }; + + if (notifications && docLinks) { + const toast = notifications.toasts.add({ + title: this.props.intl.formatMessage({ + id: 'data.query.queryBar.KQLNestedQuerySyntaxInfoTitle', + defaultMessage: 'KQL nested query syntax', + }), + text: ( +
+

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

+ + + onKQLNestedQuerySyntaxInfoOptOut(toast)}> + + + + +
+ ), + }); + } + } + }; + private increaseLimit = () => { this.setState({ suggestionLimit: this.state.suggestionLimit + 50, @@ -365,7 +429,7 @@ export class QueryBarInputUI extends Component { body: JSON.stringify({ opt_in: language === 'kuery' }), }); - this.services.store.set('kibana.userQueryLanguage', language); + this.services.storage.set('kibana.userQueryLanguage', language); const newQuery = { query: '', language }; this.onChange(newQuery); @@ -387,10 +451,10 @@ export class QueryBarInputUI extends Component { }; private initPersistedLog = () => { - const { uiSettings, store, appName } = this.services; + const { uiSettings, storage, appName } = this.services; this.persistedLog = this.props.persistedLog ? this.props.persistedLog - : getQueryLog(uiSettings, store, appName, this.props.query.language); + : getQueryLog(uiSettings, storage, appName, this.props.query.language); }; public onMouseEnterSuggestion = (index: number) => { diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx index 7ab191062e32dc..ae08083f82af3e 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx @@ -29,12 +29,11 @@ import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; const startMock = coreMock.createStart(); -import { timefilterServiceMock } from '../../../timefilter/timefilter_service.mock'; -const timefilterSetupMock = timefilterServiceMock.createSetupContract(); - -timefilterSetupMock.history.get.mockImplementation(() => { - return []; -}); +const mockTimeHistory = { + get: () => { + return []; + }, +}; startMock.uiSettings.get.mockImplementation((key: string) => { switch (key) { @@ -79,7 +78,7 @@ const createMockWebStorage = () => ({ }); const createMockStorage = () => ({ - store: createMockWebStorage(), + storage: createMockWebStorage(), get: jest.fn(), set: jest.fn(), remove: jest.fn(), @@ -112,7 +111,7 @@ function wrapQueryBarTopRowInContext(testProps: any) { const services = { ...startMock, appName: 'discover', - store: createMockStorage(), + storage: createMockStorage(), }; return ( @@ -140,7 +139,7 @@ describe('QueryBarTopRowTopRow', () => { screenTitle: 'Another Screen', isDirty: false, indexPatterns: [mockIndexPattern], - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); @@ -154,7 +153,7 @@ describe('QueryBarTopRowTopRow', () => { query: kqlQuery, screenTitle: 'Another Screen', indexPatterns: [mockIndexPattern], - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, disableAutoFocus: true, isDirty: false, }) @@ -167,7 +166,7 @@ describe('QueryBarTopRowTopRow', () => { const component = mount( wrapQueryBarTopRowInContext({ isDirty: false, - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); @@ -179,7 +178,7 @@ describe('QueryBarTopRowTopRow', () => { const component = mount( wrapQueryBarTopRowInContext({ showDatePicker: false, - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, isDirty: false, }) ); @@ -196,7 +195,7 @@ describe('QueryBarTopRowTopRow', () => { showDatePicker: true, dateRangeFrom: 'now-7d', dateRangeTo: 'now', - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); @@ -212,7 +211,7 @@ describe('QueryBarTopRowTopRow', () => { showDatePicker: true, dateRangeFrom: 'now-7d', dateRangeTo: 'now', - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); @@ -232,7 +231,7 @@ describe('QueryBarTopRowTopRow', () => { showDatePicker: false, dateRangeFrom: 'now-7d', dateRangeTo: 'now', - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); @@ -249,7 +248,7 @@ describe('QueryBarTopRowTopRow', () => { indexPatterns: [mockIndexPattern], showQueryInput: false, showDatePicker: false, - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); @@ -263,7 +262,7 @@ describe('QueryBarTopRowTopRow', () => { isDirty: false, screenTitle: 'Another Screen', showDatePicker: false, - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, }) ); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx index 9a846ab82f47cf..d31ac2d76d0d9a 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx @@ -21,7 +21,7 @@ import dateMath from '@elastic/datemath'; import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query'; import classNames from 'classnames'; -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { EuiButton, @@ -35,15 +35,14 @@ import { import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { Toast } from 'src/core/public'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange, TimeHistoryContract } from 'src/plugins/data/public'; import { useKibana } from '../../../../../../../plugins/kibana_react/public'; +import { PersistedLog } from '../../../../../../../plugins/data/public'; import { IndexPattern } from '../../../index_patterns'; import { QueryBarInput } from './query_bar_input'; import { Query, getQueryLog } from '../index'; -import { TimeHistoryContract } from '../../../timefilter'; import { IDataPluginServices } from '../../../types'; -import { PersistedLog } from '../../persisted_log'; interface Props { query?: Query; @@ -73,17 +72,15 @@ function QueryBarTopRowUI(props: Props) { const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false); const kibana = useKibana(); - const { uiSettings, notifications, store, appName, docLinks } = kibana.services; + const { uiSettings, notifications, storage, appName, docLinks } = kibana.services; const kueryQuerySyntaxLink: string = docLinks!.links.query.kueryQuerySyntax; const queryLanguage = props.query && props.query.language; - let persistedLog: PersistedLog | undefined; - - useEffect(() => { - if (!props.query) return; - persistedLog = getQueryLog(uiSettings!, store, appName, props.query.language); - }, [queryLanguage]); + const persistedLog: PersistedLog | undefined = React.useMemo( + () => (queryLanguage ? getQueryLog(uiSettings!, storage, appName, queryLanguage) : undefined), + [queryLanguage] + ); function onClickSubmitButton(event: React.MouseEvent) { if (persistedLog && props.query) { @@ -211,7 +208,7 @@ function QueryBarTopRowUI(props: Props) { } function shouldRenderQueryInput(): boolean { - return Boolean(props.showQueryInput && props.indexPatterns && props.query && store); + return Boolean(props.showQueryInput && props.indexPatterns && props.query && storage); } function renderUpdateButton() { @@ -293,7 +290,7 @@ function QueryBarTopRowUI(props: Props) { if ( language === 'kuery' && typeof query === 'string' && - (!store || !store.get('kibana.luceneSyntaxWarningOptOut')) && + (!storage || !storage.get('kibana.luceneSyntaxWarningOptOut')) && doesKueryExpressionHaveLuceneSyntaxError(query) ) { const toast = notifications!.toasts.addWarning({ @@ -337,8 +334,8 @@ function QueryBarTopRowUI(props: Props) { } function onLuceneSyntaxWarningOptOut(toast: Toast) { - if (!store) return; - store.set('kibana.luceneSyntaxWarningOptOut', true); + if (!storage) return; + storage.set('kibana.luceneSyntaxWarningOptOut', true); notifications!.toasts.remove(toast); } diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts b/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts index 8b26e14c6ed7bd..66424d9a1d6a35 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts +++ b/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts @@ -18,12 +18,12 @@ */ import { UiSettingsClientContract } from 'src/core/public'; -import { PersistedLog } from '../../persisted_log'; -import { Storage } from '../../../types'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { PersistedLog } from '../../../../../../../plugins/data/public'; export function getQueryLog( uiSettings: UiSettingsClientContract, - store: Storage, + storage: IStorageWrapper, appName: string, language: string ) { @@ -33,6 +33,6 @@ export function getQueryLog( maxLength: uiSettings.get('history:limit'), filterDuplicates: true, }, - store + storage ); } diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx index d801f8a69e2d64..4485b74ca09010 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx @@ -19,52 +19,45 @@ import React, { useState, useEffect } from 'react'; import { Subscription } from 'rxjs'; -import { Filter } from '@kbn/es-query'; import { CoreStart } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { Storage } from '../../../types'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; -import { TimefilterSetup } from '../../../timefilter'; -import { FilterManager, SearchBar } from '../../../'; +import { SearchBar } from '../../../'; import { SearchBarOwnProps } from '.'; +import { esFilters } from '../../../../../../../plugins/data/public'; interface StatefulSearchBarDeps { core: CoreStart; data: DataPublicPluginStart; - store: Storage; - timefilter: TimefilterSetup; - filterManager: FilterManager; + storage: IStorageWrapper; } export type StatetfulSearchBarProps = SearchBarOwnProps & { appName: string; }; -const defaultFiltersUpdated = (filterManager: FilterManager) => { - return (filters: Filter[]) => { - filterManager.setFilters(filters); +const defaultFiltersUpdated = (data: DataPublicPluginStart) => { + return (filters: esFilters.Filter[]) => { + data.query.filterManager.setFilters(filters); }; }; -const defaultOnRefreshChange = (timefilter: TimefilterSetup) => { +const defaultOnRefreshChange = (data: DataPublicPluginStart) => { + const { timefilter } = data.query.timefilter; return (options: { isPaused: boolean; refreshInterval: number }) => { - timefilter.timefilter.setRefreshInterval({ + timefilter.setRefreshInterval({ value: options.refreshInterval, pause: options.isPaused, }); }; }; -export function createSearchBar({ - core, - store, - timefilter, - filterManager, - data, -}: StatefulSearchBarDeps) { +export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) { // App name should come from the core application service. // Until it's available, we'll ask the user to provide it for the pre-wired component. return (props: StatetfulSearchBarProps) => { + const { filterManager, timefilter } = data.query; const tfRefreshInterval = timefilter.timefilter.getRefreshInterval(); const fmFilters = filterManager.getFilters(); const [refreshInterval, setRefreshInterval] = useState(tfRefreshInterval.value); @@ -113,7 +106,7 @@ export function createSearchBar({ services={{ appName: props.appName, data, - store, + storage, ...core, }} > @@ -124,8 +117,8 @@ export function createSearchBar({ refreshInterval={refreshInterval} isRefreshPaused={refreshPaused} filters={filters} - onFiltersUpdated={defaultFiltersUpdated(filterManager)} - onRefreshChange={defaultOnRefreshChange(timefilter)} + onFiltersUpdated={defaultFiltersUpdated(data)} + onRefreshChange={defaultOnRefreshChange(data)} {...props} /> diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx index 6fd5274d09ec68..7a9786f5f9ce82 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx @@ -83,7 +83,7 @@ export const SaveQueryForm: FunctionComponent = ({ setSavedQueries(sortedAllSavedQueries); }; fetchQueries(); - }, []); + }, [savedQueryService]); const savedQueryDescriptionText = i18n.translate( 'data.search.searchBar.savedQueryDescriptionText', diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx index b2e47e8b4e850a..b73b8edb39e547 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx @@ -29,6 +29,7 @@ import { EuiPagination, EuiText, EuiSpacer, + EuiIcon, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -75,7 +76,7 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ if (isOpen) { fetchCountAndSavedQueries(); } - }, [isOpen, activePage]); + }, [isOpen, activePage, savedQueryService]); const goToPage = (pageNumber: number) => { setActivePage(pageNumber); @@ -116,8 +117,6 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ const savedQueryPopoverButton = ( { setIsOpen(!isOpen); }} @@ -129,7 +128,8 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ })} data-test-subj="saved-query-management-popover-button" > - # + + ); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx index 73e81a38572c39..44637365247fbd 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx @@ -27,20 +27,24 @@ import { I18nProvider } from '@kbn/i18n/react'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; const startMock = coreMock.createStart(); -import { timefilterServiceMock } from '../../../timefilter/timefilter_service.mock'; import { mount } from 'enzyme'; -const timefilterSetupMock = timefilterServiceMock.createSetupContract(); + +const mockTimeHistory = { + get: () => { + return []; + }, +}; jest.mock('../../../../../data/public', () => { return { - FilterBar: () =>
, - QueryBarInput: () =>
, + FilterBar: () =>
, + QueryBarInput: () =>
, }; }); jest.mock('../../../query/query_bar', () => { return { - QueryBarTopRow: () =>
, + QueryBarTopRow: () =>
, }; }); @@ -56,7 +60,7 @@ const createMockWebStorage = () => ({ }); const createMockStorage = () => ({ - store: createMockWebStorage(), + storage: createMockWebStorage(), get: jest.fn(), set: jest.fn(), remove: jest.fn(), @@ -86,7 +90,7 @@ const kqlQuery = { function wrapSearchBarInContext(testProps: any) { const defaultOptions = { appName: 'test', - timeHistory: timefilterSetupMock.history, + timeHistory: mockTimeHistory, intl: null as any, }; @@ -95,7 +99,7 @@ function wrapSearchBarInContext(testProps: any) { savedObjects: startMock.savedObjects, notifications: startMock.notifications, http: startMock.http, - store: createMockStorage(), + storage: createMockStorage(), }; return ( diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx index a03019da4e0d7a..a57b7b17a0da66 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx @@ -18,7 +18,6 @@ */ import { compact } from 'lodash'; -import { Filter } from '@kbn/es-query'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { Component } from 'react'; @@ -26,6 +25,7 @@ import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; import { TimeRange } from 'src/plugins/data/common/types'; +import { TimeHistoryContract } from 'src/plugins/data/public'; import { IndexPattern, Query, FilterBar } from '../../../../../data/public'; import { QueryBarTopRow } from '../../../query'; import { SavedQuery, SavedQueryAttributes } from '../index'; @@ -33,20 +33,20 @@ import { SavedQueryMeta, SaveQueryForm } from './saved_query_management/save_que import { SavedQueryManagementComponent } from './saved_query_management/saved_query_management_component'; import { SavedQueryService } from '../lib/saved_query_service'; import { createSavedQueryService } from '../lib/saved_query_service'; -import { TimeHistoryContract } from '../../../timefilter'; import { withKibana, KibanaReactContextValue, } from '../../../../../../../plugins/kibana_react/public'; import { IDataPluginServices } from '../../../types'; +import { esFilters } from '../../../../../../../plugins/data/public'; interface SearchBarInjectedDeps { kibana: KibanaReactContextValue; intl: InjectedIntl; timeHistory: TimeHistoryContract; // Filter bar - onFiltersUpdated?: (filters: Filter[]) => void; - filters?: Filter[]; + onFiltersUpdated?: (filters: esFilters.Filter[]) => void; + filters?: esFilters.Filter[]; // Date picker dateRangeFrom?: string; dateRangeTo?: string; @@ -368,7 +368,7 @@ class SearchBarUI extends Component { onLoad={this.onLoadSavedQuery} savedQueryService={this.savedQueryService} onClearSavedQuery={this.props.onClearSavedQuery} - > + /> ); let queryBar; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx index 0c677bea985365..ebde9d60b0b51e 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx @@ -17,9 +17,9 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; import { Query } from '../../query/query_bar'; +import { esFilters } from '../../../../../../plugins/data/public'; export * from './components'; @@ -36,6 +36,6 @@ export interface SavedQueryAttributes { title: string; description: string; query: Query; - filters?: Filter[]; + filters?: esFilters.Filter[]; timefilter?: SavedQueryTimeFilter; } diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts b/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts index ac5fdb7fe99d54..415da8a2c32cc1 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts +++ b/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts @@ -19,7 +19,7 @@ import { SavedQueryAttributes } from '../index'; import { createSavedQueryService } from './saved_query_service'; -import { FilterStateStore } from '@kbn/es-query'; +import { esFilters } from '../../../../../../../plugins/data/public'; const savedQueryAttributes: SavedQueryAttributes = { title: 'foo', @@ -43,7 +43,7 @@ const savedQueryAttributesWithFilters: SavedQueryAttributes = { filters: [ { query: { match_all: {} }, - $state: { store: FilterStateStore.APP_STATE }, + $state: { store: esFilters.FilterStateStore.APP_STATE }, meta: { disabled: false, negate: false, diff --git a/src/legacy/core_plugins/data/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/data/public/shim/legacy_dependencies_plugin.ts deleted file mode 100644 index 5b12d56dc7b00c..00000000000000 --- a/src/legacy/core_plugins/data/public/shim/legacy_dependencies_plugin.ts +++ /dev/null @@ -1,47 +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 { Storage } from 'ui/storage'; -import { Plugin } from '../../../../../../src/core/public'; -import { initLegacyModule } from './legacy_module'; - -/** @internal */ -export interface LegacyDependenciesPluginSetup { - storage: Storage; -} - -export interface LegacyDependenciesPluginStart { - storage: Storage; -} - -export class LegacyDependenciesPlugin implements Plugin { - public setup() { - initLegacyModule(); - - return { - storage: new Storage(window.localStorage), - } as LegacyDependenciesPluginSetup; - } - - public start() { - return { - storage: new Storage(window.localStorage), - } as LegacyDependenciesPluginStart; - } -} diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index 0b5ca72599208a..b0ed3d43a4c8cf 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -25,13 +25,10 @@ import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; import { npStart } from 'ui/new_platform'; import { FilterBar, ApplyFiltersPopover } from '../filter'; - -// @ts-ignore -import { mapAndFlattenFilters } from '../filter/filter_manager/lib/map_and_flatten_filters'; import { IndexPatterns } from '../index_patterns/index_patterns'; /** @internal */ -export const initLegacyModule = once((): void => { +export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { uiModules .get('app/kibana', ['react']) .directive('filterBar', () => { @@ -122,16 +119,5 @@ export const initLegacyModule = once((): void => { ]) ); - const module = uiModules.get('kibana/index_patterns'); - let _service: any; - module.service('indexPatterns', function() { - if (!_service) - _service = new IndexPatterns( - npStart.core.uiSettings, - npStart.core.savedObjects.client, - npStart.core.http, - npStart.core.notifications - ); - return _service; - }); + uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/data/public/timefilter/index.ts b/src/legacy/core_plugins/data/public/timefilter/index.ts deleted file mode 100644 index 17564801cf1481..00000000000000 --- a/src/legacy/core_plugins/data/public/timefilter/index.ts +++ /dev/null @@ -1,25 +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. - */ - -export { TimefilterService, TimefilterSetup } from './timefilter_service'; - -export * from './types'; -export { Timefilter, TimefilterContract } from './timefilter'; -export { TimeHistory, TimeHistoryContract } from './time_history'; -export { getTime } from './get_time'; diff --git a/src/legacy/core_plugins/data/public/types.ts b/src/legacy/core_plugins/data/public/types.ts index 2c02a9b7647555..b6c9c47cc0ae69 100644 --- a/src/legacy/core_plugins/data/public/types.ts +++ b/src/legacy/core_plugins/data/public/types.ts @@ -19,13 +19,7 @@ import { UiSettingsClientContract, CoreStart } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; - -export interface Storage { - get: (key: string) => any; - set: (key: string, value: any) => void; - remove: (key: string) => any; - clear: () => void; -} +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; export interface IDataPluginServices extends Partial { appName: string; @@ -33,6 +27,6 @@ export interface IDataPluginServices extends Partial { savedObjects: CoreStart['savedObjects']; notifications: CoreStart['notifications']; http: CoreStart['http']; - store: Storage; + storage: IStorageWrapper; data: DataPublicPluginStart; } diff --git a/src/legacy/core_plugins/elasticsearch/index.js b/src/legacy/core_plugins/elasticsearch/index.js index ed6e2d7f7f1c8d..f92f920d1afb74 100644 --- a/src/legacy/core_plugins/elasticsearch/index.js +++ b/src/legacy/core_plugins/elasticsearch/index.js @@ -98,10 +98,14 @@ export default function (kibana) { createProxy(server); // Set up the health check service and start it. - const { start, waitUntilReady } = healthCheck(this, server, esConfig.healthCheckDelay.asMilliseconds()); + const { start, waitUntilReady } = healthCheck( + this, + server, + esConfig.healthCheckDelay.asMilliseconds(), + esConfig.ignoreVersionMismatch + ); server.expose('waitUntilReady', waitUntilReady); start(); - } + }, }); - } diff --git a/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js b/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js index baf728262aad1e..a31c7830f8d5c7 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js +++ b/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js @@ -105,6 +105,51 @@ describe('plugins/elasticsearch', () => { } }); + it('does not throw on outdated nodes, if `ignoreVersionMismatch` is enabled in development mode', async () => { + // set config values + server.config = () => ({ + get: name => { + switch (name) { + case 'env.dev': + return true; + default: + throw new Error(`Unknown option "${name}"`); + } + }, + }); + + // 5.0.0 ES is too old to work with a 5.1.0 version of Kibana. + setNodes('5.1.0', '5.2.0', '5.0.0'); + + const ignoreVersionMismatch = true; + const result = await ensureEsVersion(server, KIBANA_VERSION, ignoreVersionMismatch); + expect(result).to.be(true); + }); + + it('throws an error if `ignoreVersionMismatch` is enabled in production mode', async () => { + // set config values + server.config = () => ({ + get: name => { + switch (name) { + case 'env.dev': + return false; + default: + throw new Error(`Unknown option "${name}"`); + } + }, + }); + + // 5.0.0 ES is too old to work with a 5.1.0 version of Kibana. + setNodes('5.1.0', '5.2.0', '5.0.0'); + + try { + const ignoreVersionMismatch = true; + await ensureEsVersion(server, KIBANA_VERSION, ignoreVersionMismatch); + } catch (e) { + expect(e).to.be.a(Error); + } + }); + it('fails if that single node is a client node', async () => { setNodes( '5.1.0', diff --git a/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js b/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js index 95c7db36d5fb4e..cb3ed1886b4a12 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js +++ b/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js @@ -36,7 +36,7 @@ import isEsCompatibleWithKibana from './is_es_compatible_with_kibana'; */ const lastWarnedNodesForServer = new WeakMap(); -export function ensureEsVersion(server, kibanaVersion) { +export function ensureEsVersion(server, kibanaVersion, ignoreVersionMismatch = false) { const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); server.logWithMetadata(['plugin', 'debug'], 'Checking Elasticsearch version'); @@ -102,7 +102,7 @@ export function ensureEsVersion(server, kibanaVersion) { } } - if (incompatibleNodes.length) { + if (incompatibleNodes.length && !shouldIgnoreVersionMismatch(server, ignoreVersionMismatch)) { const incompatibleNodeNames = getHumanizedNodeNames(incompatibleNodes); throw new Error( `This version of Kibana requires Elasticsearch v` + @@ -114,3 +114,12 @@ export function ensureEsVersion(server, kibanaVersion) { return true; }); } + +function shouldIgnoreVersionMismatch(server, ignoreVersionMismatch) { + const isDevMode = server.config().get('env.dev'); + if(!isDevMode && ignoreVersionMismatch) { + throw new Error(`Option "elasticsearch.ignoreVersionMismatch" can only be used in development mode`); + } + + return isDevMode && ignoreVersionMismatch; +} diff --git a/src/legacy/core_plugins/elasticsearch/lib/health_check.js b/src/legacy/core_plugins/elasticsearch/lib/health_check.js index 1b05d51b02494c..ac8e6caab94960 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/health_check.js +++ b/src/legacy/core_plugins/elasticsearch/lib/health_check.js @@ -21,7 +21,7 @@ import Bluebird from 'bluebird'; import kibanaVersion from './kibana_version'; import { ensureEsVersion } from './ensure_es_version'; -export default function (plugin, server, requestDelay) { +export default function (plugin, server, requestDelay, ignoreVersionMismatch) { plugin.status.yellow('Waiting for Elasticsearch'); function waitUntilReady() { @@ -31,7 +31,7 @@ export default function (plugin, server, requestDelay) { } function check() { - return ensureEsVersion(server, kibanaVersion.get()) + return ensureEsVersion(server, kibanaVersion.get(), ignoreVersionMismatch) .then(() => plugin.status.green('Ready')) .catch(err => plugin.status.red(err)); } diff --git a/src/legacy/core_plugins/embeddable_api/public/index.scss b/src/legacy/core_plugins/embeddable_api/public/index.scss index b4e170e6cb65c7..3f1977b909c31c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/index.scss +++ b/src/legacy/core_plugins/embeddable_api/public/index.scss @@ -1,5 +1,3 @@ @import 'src/legacy/ui/public/styles/styling_constants'; -@import './variables'; -@import '../../../../plugins/embeddable/public/lib/panel/index'; -@import '../../../../plugins/embeddable/public/lib/panel/panel_header/index'; +@import '../../../../plugins/embeddable/public/index'; diff --git a/src/legacy/core_plugins/expressions/index.ts b/src/legacy/core_plugins/expressions/index.ts index b10e9a8dd5442a..4ba9a74795d4c9 100644 --- a/src/legacy/core_plugins/expressions/index.ts +++ b/src/legacy/core_plugins/expressions/index.ts @@ -34,6 +34,7 @@ export default function DataExpressionsPlugin(kibana: any) { init: (server: Legacy.Server) => ({}), uiExports: { injectDefaultVars: () => ({}), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, }; diff --git a/src/legacy/core_plugins/expressions/public/index.scss b/src/legacy/core_plugins/expressions/public/index.scss new file mode 100644 index 00000000000000..6efc552bf319bd --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/index.scss @@ -0,0 +1,13 @@ +// Import the EUI global scope so we can use EUI constants +@import 'src/legacy/ui/public/styles/_styling_constants'; + +/* Expressions plugin styles */ + +// Prefix all styles with "exp" to avoid conflicts. +// Examples +// expChart +// expChart__legend +// expChart__legend--small +// expChart__legend-isLoading + +@import './np_ready/public/index'; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/_expression_renderer.scss b/src/legacy/core_plugins/expressions/public/np_ready/public/_expression_renderer.scss new file mode 100644 index 00000000000000..4f030384ed883d --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/_expression_renderer.scss @@ -0,0 +1,20 @@ +.expExpressionRenderer { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.expExpressionRenderer__expression { + width: 100%; + height: 100%; +} + +.expExpressionRenderer-isEmpty, +.expExpressionRenderer-hasError { + .expExpressionRenderer__expression { + display: none; + } +} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss b/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss new file mode 100644 index 00000000000000..b9df491cd6e790 --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss @@ -0,0 +1 @@ +@import './expression_renderer'; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts index b4b11588b91bf4..8043e0fb6e3f98 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts @@ -61,6 +61,7 @@ export class ExpressionDataHandler { this.promise = interpreter.interpretAst(this.ast, params.context || defaultContext, { getInitialContext, inspectorAdapters: this.inspectorAdapters, + abortSignal: this.abortController.signal, }); } @@ -69,7 +70,18 @@ export class ExpressionDataHandler { }; getData = async () => { - return await this.promise; + try { + return await this.promise; + } catch (e) { + return { + type: 'error', + error: { + type: e.type, + message: e.message, + stack: e.stack, + }, + }; + } }; getExpression = () => { diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.test.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.test.tsx new file mode 100644 index 00000000000000..26db8753e64035 --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.test.tsx @@ -0,0 +1,141 @@ +/* + * 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 { Subject } from 'rxjs'; +import { share } from 'rxjs/operators'; +import { ExpressionRendererImplementation } from './expression_renderer'; +import { ExpressionLoader } from './loader'; +import { mount } from 'enzyme'; +import { EuiProgress } from '@elastic/eui'; + +jest.mock('./loader', () => { + return { + ExpressionLoader: jest.fn().mockImplementation(() => { + return {}; + }), + loader: jest.fn(), + }; +}); + +describe('ExpressionRenderer', () => { + it('starts to load, resolves, and goes back to loading', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + const renderSubject = new Subject(); + const render$ = renderSubject.asObservable().pipe(share()); + const loadingSubject = new Subject(); + const loading$ = loadingSubject.asObservable().pipe(share()); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$, + data$, + loading$, + update: jest.fn(), + }; + }); + + const instance = mount(); + + loadingSubject.next(); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(1); + + renderSubject.next(1); + + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(0); + + instance.setProps({ expression: 'something new' }); + loadingSubject.next(); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(1); + + renderSubject.next(1); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(0); + }); + + it('should display an error message when the expression fails', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + const renderSubject = new Subject(); + const render$ = renderSubject.asObservable().pipe(share()); + const loadingSubject = new Subject(); + const loading$ = loadingSubject.asObservable().pipe(share()); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$, + data$, + loading$, + update: jest.fn(), + }; + }); + + const instance = mount(); + + dataSubject.next('good data'); + renderSubject.next({ + type: 'error', + error: { message: 'render error' }, + }); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(0); + expect(instance.find('[data-test-subj="expression-renderer-error"]')).toHaveLength(1); + }); + + it('should display a custom error message if the user provides one', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + const renderSubject = new Subject(); + const render$ = renderSubject.asObservable().pipe(share()); + const loadingSubject = new Subject(); + const loading$ = loadingSubject.asObservable().pipe(share()); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$, + data$, + loading$, + update: jest.fn(), + }; + }); + + const renderErrorFn = jest.fn().mockReturnValue(null); + + const instance = mount( + + ); + + renderSubject.next({ + type: 'error', + error: { message: 'render error' }, + }); + instance.update(); + + expect(renderErrorFn).toHaveBeenCalledWith('render error'); + }); +}); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx index 9edb5f098ba2e7..208bdbe5d0288f 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx @@ -17,49 +17,50 @@ * under the License. */ -import { useRef, useEffect } from 'react'; +import { useRef, useEffect, useState } from 'react'; import React from 'react'; -import { ExpressionAST, IExpressionLoaderParams, IInterpreterResult } from './types'; -import { IExpressionLoader, ExpressionLoader } from './loader'; +import classNames from 'classnames'; +import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; +import { ExpressionAST, IExpressionLoaderParams } from './types'; +import { ExpressionLoader } from './loader'; // Accept all options of the runner as props except for the // dom element which is provided by the component itself export interface ExpressionRendererProps extends IExpressionLoaderParams { - className: string; dataAttrs?: string[]; expression: string | ExpressionAST; - /** - * If an element is specified, but the response of the expression run can't be rendered - * because it isn't a valid response or the specified renderer isn't available, - * this callback is called with the given result. - */ - onRenderFailure?: (result: IInterpreterResult) => void; + renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; +} + +interface State { + isEmpty: boolean; + isLoading: boolean; + error: null | { message: string }; } export type ExpressionRenderer = React.FC; -export const createRenderer = (loader: IExpressionLoader): ExpressionRenderer => ({ - className, +const defaultState: State = { + isEmpty: true, + isLoading: false, + error: null, +}; + +export const ExpressionRendererImplementation = ({ dataAttrs, expression, - onRenderFailure, + renderError, ...options }: ExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); const handlerRef: React.MutableRefObject = useRef(null); + const [state, setState] = useState({ ...defaultState }); + // Re-fetch data automatically when the inputs change + /* eslint-disable react-hooks/exhaustive-deps */ useEffect(() => { - if (mountpoint.current) { - if (!handlerRef.current) { - handlerRef.current = loader(mountpoint.current, expression, options); - } else { - handlerRef.current.update(expression, options); - } - handlerRef.current.data$.toPromise().catch(result => { - if (onRenderFailure) { - onRenderFailure(result); - } - }); + if (handlerRef.current) { + handlerRef.current.update(expression, options); } }, [ expression, @@ -67,8 +68,67 @@ export const createRenderer = (loader: IExpressionLoader): ExpressionRenderer => options.context, options.variables, options.disableCaching, - mountpoint.current, ]); + /* eslint-enable react-hooks/exhaustive-deps */ + + // Initialize the loader only once + useEffect(() => { + if (mountpoint.current && !handlerRef.current) { + handlerRef.current = new ExpressionLoader(mountpoint.current, expression, options); + + handlerRef.current.loading$.subscribe(() => { + if (!handlerRef.current) { + return; + } + setState(prevState => ({ ...prevState, isLoading: true })); + }); + handlerRef.current.render$.subscribe(item => { + if (!handlerRef.current) { + return; + } + if (typeof item !== 'number') { + setState(() => ({ + ...defaultState, + isEmpty: false, + error: item.error, + })); + } else { + setState(() => ({ + ...defaultState, + isEmpty: false, + })); + } + }); + } + }, [mountpoint.current]); + + useEffect(() => { + // We only want a clean up to run when the entire component is unloaded, not on every render + return function cleanup() { + if (handlerRef.current) { + handlerRef.current.destroy(); + handlerRef.current = null; + } + }; + }, []); + + const classes = classNames('expExpressionRenderer', { + 'expExpressionRenderer-isEmpty': state.isEmpty, + 'expExpressionRenderer-hasError': !!state.error, + }); - return
; + return ( +
+ {state.isEmpty ? : null} + {state.isLoading ? : null} + {!state.isLoading && state.error ? ( + renderError ? ( + renderError(state.error.message) + ) : ( +
{state.error.message}
+ ) + ) : null} +
+
+ ); }; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts index 428b431d298ad9..2ff71a6df60cf8 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts @@ -22,7 +22,7 @@ import { ExpressionsPublicPlugin } from './plugin'; export * from './plugin'; export { ExpressionRenderer, ExpressionRendererProps } from './expression_renderer'; -export { IInterpreterRenderFunction } from './types'; +export { IInterpreterRenderFunction, IInterpreterRenderHandlers } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new ExpressionsPublicPlugin(initializerContext); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts index df695c039e02ed..a3caa1c47b150b 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts @@ -19,10 +19,11 @@ import { first } from 'rxjs/operators'; import { loader, ExpressionLoader } from './loader'; +import { ExpressionDataHandler } from './execute'; import { fromExpression } from '@kbn/interpreter/common'; import { IInterpreterRenderHandlers } from './types'; import { Observable } from 'rxjs'; -import { ExpressionAST } from '../../../../../../plugins/expressions/common'; +import { ExpressionAST } from '../../../../../../plugins/expressions/public'; const element: HTMLElement = null as any; @@ -48,27 +49,37 @@ jest.mock('./services', () => { }; }); +jest.mock('./execute', () => { + const actual = jest.requireActual('./execute'); + return { + ExpressionDataHandler: jest + .fn() + .mockImplementation((...args) => new actual.ExpressionDataHandler(...args)), + execute: jest.fn().mockReturnValue(actual.execute), + }; +}); + describe('execute helper function', () => { - it('returns ExpressionDataHandler instance', () => { + it('returns ExpressionLoader instance', () => { const response = loader(element, '', {}); expect(response).toBeInstanceOf(ExpressionLoader); }); }); -describe('ExpressionDataHandler', () => { +describe('ExpressionLoader', () => { const expressionString = ''; describe('constructor', () => { it('accepts expression string', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + expect(expressionLoader.getExpression()).toEqual(expressionString); }); it('accepts expression AST', () => { const expressionAST = fromExpression(expressionString) as ExpressionAST; - const expressionDataHandler = new ExpressionLoader(element, expressionAST, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - expect(expressionDataHandler.getAst()).toEqual(expressionAST); + const expressionLoader = new ExpressionLoader(element, expressionAST, {}); + expect(expressionLoader.getExpression()).toEqual(expressionString); + expect(expressionLoader.getAst()).toEqual(expressionAST); }); it('creates observables', () => { @@ -117,9 +128,86 @@ describe('ExpressionDataHandler', () => { expect(response).toBe(2); }); - it('cancel() aborts request', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expressionDataHandler.cancel(); + it('cancels the previous request when the expression is updated', () => { + const cancelMock = jest.fn(); + + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData: () => true, + cancel: cancelMock, + })); + + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + expressionLoader.update('new', {}); + + expect(cancelMock).toHaveBeenCalledTimes(1); + }); + + it('does not send an observable message if a request was aborted', () => { + const cancelMock = jest.fn(); + + const getData = jest + .fn() + .mockResolvedValueOnce({ + type: 'error', + error: { + name: 'AbortError', + }, + }) + .mockResolvedValueOnce({ + type: 'real', + }); + + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData, + cancel: cancelMock, + })); + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData, + cancel: cancelMock, + })); + + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + + expect.assertions(2); + expressionLoader.data$.subscribe({ + next(data) { + expect(data).toEqual({ + type: 'real', + }); + }, + error() { + expect(false).toEqual('Should not be called'); + }, + }); + + expressionLoader.update('new expression', {}); + + expect(getData).toHaveBeenCalledTimes(2); + }); + + it('sends an observable error if the data fetching failed', () => { + const cancelMock = jest.fn(); + + const getData = jest.fn().mockResolvedValue('rejected'); + + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData, + cancel: cancelMock, + })); + + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + + expect.assertions(2); + expressionLoader.data$.subscribe({ + next(data) { + expect(data).toEqual('Should not be called'); + }, + error(error) { + expect(error.message).toEqual('Could not fetch data'); + }, + }); + + expect(getData).toHaveBeenCalledTimes(1); }); it('inspect() returns correct inspector adapters', () => { diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts index b07b0048bfd521..709fbc78a9b524 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts @@ -18,11 +18,11 @@ */ import { Observable, Subject } from 'rxjs'; -import { first, share } from 'rxjs/operators'; +import { share } from 'rxjs/operators'; import { Adapters, InspectorSession } from '../../../../../../plugins/inspector/public'; -import { execute, ExpressionDataHandler } from './execute'; +import { ExpressionDataHandler } from './execute'; import { ExpressionRenderHandler } from './render'; -import { RenderId, Data, IExpressionLoaderParams, ExpressionAST } from './types'; +import { Data, IExpressionLoaderParams, ExpressionAST } from './types'; import { getInspector } from './services'; export class ExpressionLoader { @@ -32,12 +32,12 @@ export class ExpressionLoader { events$: ExpressionRenderHandler['events$']; loading$: Observable; - private dataHandler!: ExpressionDataHandler; + private dataHandler: ExpressionDataHandler | undefined; private renderHandler: ExpressionRenderHandler; private dataSubject: Subject; private loadingSubject: Subject; private data: Data; - private params: IExpressionLoaderParams; + private params: IExpressionLoaderParams = {}; constructor( element: HTMLElement, @@ -63,76 +63,108 @@ export class ExpressionLoader { this.render(data); }); - this.params = { - searchContext: { type: 'kibana_context' }, - extraHandlers: params.extraHandlers, - }; + this.setParams(params); - this.execute(expression, params); + this.loadData(expression, this.params); } - destroy() {} + destroy() { + this.dataSubject.complete(); + this.loadingSubject.complete(); + this.renderHandler.destroy(); + if (this.dataHandler) { + this.dataHandler.cancel(); + } + } cancel() { - this.dataHandler.cancel(); + if (this.dataHandler) { + this.dataHandler.cancel(); + } } - getExpression(): string { - return this.dataHandler.getExpression(); + getExpression(): string | undefined { + if (this.dataHandler) { + return this.dataHandler.getExpression(); + } } - getAst(): ExpressionAST { - return this.dataHandler.getAst(); + getAst(): ExpressionAST | undefined { + if (this.dataHandler) { + return this.dataHandler.getAst(); + } } getElement(): HTMLElement { return this.renderHandler.getElement(); } - openInspector(title: string): InspectorSession { - return getInspector().open(this.inspect(), { - title, - }); + openInspector(title: string): InspectorSession | undefined { + const inspector = this.inspect(); + if (inspector) { + return getInspector().open(inspector, { + title, + }); + } } - inspect(): Adapters { - return this.dataHandler.inspect(); + inspect(): Adapters | undefined { + if (this.dataHandler) { + return this.dataHandler.inspect(); + } } - update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): Promise { - const promise = this.render$.pipe(first()).toPromise(); - if (params && params.searchContext && this.params.searchContext) { - this.params.searchContext = _.defaults( - {}, - params.searchContext, - this.params.searchContext - ) as any; - } + update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): void { + this.setParams(params); - this.loadingSubject.next(); if (expression) { - this.execute(expression, this.params); + this.loadData(expression, this.params); } else { this.render(this.data); } - return promise; } - private execute = async ( + private loadData = async ( expression: string | ExpressionAST, - params?: IExpressionLoaderParams - ): Promise => { + params: IExpressionLoaderParams + ): Promise => { + this.loadingSubject.next(); if (this.dataHandler) { this.dataHandler.cancel(); } - this.dataHandler = execute(expression, params); - this.data = await this.dataHandler.getData(); - this.dataSubject.next(this.data); - return this.data; + this.setParams(params); + this.dataHandler = new ExpressionDataHandler(expression, params); + const data = await this.dataHandler.getData(); + this.dataSubject.next(data); }; - private async render(data: Data): Promise { - return this.renderHandler.render(data, this.params.extraHandlers); + private render(data: Data): void { + this.loadingSubject.next(); + this.renderHandler.render(data, this.params.extraHandlers); + } + + private setParams(params?: IExpressionLoaderParams) { + if (!params || !Object.keys(params).length) { + return; + } + + if (params.searchContext && this.params.searchContext) { + this.params.searchContext = _.defaults( + {}, + params.searchContext, + this.params.searchContext + ) as any; + } + if (params.extraHandlers && this.params) { + this.params.extraHandlers = params.extraHandlers; + } + + if (!Object.keys(this.params).length) { + this.params = { + ...params, + searchContext: { type: 'kibana_context', ...(params.searchContext || {}) }, + }; + } } } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.ts deleted file mode 100644 index 6569e8d8d1ec5b..00000000000000 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.ts +++ /dev/null @@ -1,83 +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 { ExpressionsSetup, ExpressionsStart, plugin as pluginInitializer } from '.'; -/* eslint-disable */ -import { coreMock } from '../../../../../../core/public/mocks'; -import { inspectorPluginMock } from '../../../../../../plugins/inspector/public/mocks'; -/* eslint-enable */ - -const createExpressionsSetupMock = (): ExpressionsSetup => { - return { - registerFunction: jest.fn(), - registerRenderer: jest.fn(), - registerType: jest.fn(), - __LEGACY: { - functions: { - register: () => {}, - } as any, - renderers: { - register: () => {}, - } as any, - types: { - register: () => {}, - } as any, - }, - }; -}; - -function createExpressionsStartMock(): ExpressionsStart { - return { - ExpressionRenderer: jest.fn(() => null), - execute: jest.fn(), - loader: jest.fn(), - render: jest.fn(), - ExpressionRenderHandler: jest.fn(), - ExpressionDataHandler: jest.fn(), - ExpressionLoader: jest.fn(), - }; -} - -const createPlugin = async () => { - const pluginInitializerContext = coreMock.createPluginInitializerContext(); - const coreSetup = coreMock.createSetup(); - const coreStart = coreMock.createStart(); - const plugin = pluginInitializer(pluginInitializerContext); - const setup = await plugin.setup(coreSetup, { - inspector: inspectorPluginMock.createSetupContract(), - }); - - return { - pluginInitializerContext, - coreSetup, - coreStart, - plugin, - setup, - doStart: async () => - await plugin.start(coreStart, { - inspector: inspectorPluginMock.createStartContract(), - }), - }; -}; - -export const expressionsPluginMock = { - createSetup: createExpressionsSetupMock, - createStart: createExpressionsStartMock, - createPlugin, -}; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx new file mode 100644 index 00000000000000..b17cd454b65eab --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx @@ -0,0 +1,89 @@ +/* + * 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 { ExpressionsSetup, ExpressionsStart, plugin as pluginInitializer } from '.'; +/* eslint-disable */ +import { coreMock } from '../../../../../../core/public/mocks'; +import { inspectorPluginMock } from '../../../../../../plugins/inspector/public/mocks'; +/* eslint-enable */ + +const createExpressionsSetupMock = (): ExpressionsSetup => { + return { + registerFunction: jest.fn(), + registerRenderer: jest.fn(), + registerType: jest.fn(), + __LEGACY: { + functions: { + register: () => {}, + } as any, + renderers: { + register: () => {}, + } as any, + types: { + register: () => {}, + } as any, + getExecutor: () => ({ + interpreter: { + interpretAst: () => {}, + }, + }), + }, + }; +}; + +function createExpressionsStartMock(): ExpressionsStart { + return { + ExpressionRenderer: jest.fn(props => <>), + execute: jest.fn(), + loader: jest.fn(), + render: jest.fn(), + ExpressionRenderHandler: jest.fn(), + ExpressionDataHandler: jest.fn(), + ExpressionLoader: jest.fn(), + }; +} + +const createPlugin = async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + const plugin = pluginInitializer(pluginInitializerContext); + const setup = await plugin.setup(coreSetup, { + inspector: inspectorPluginMock.createSetupContract(), + }); + + return { + pluginInitializerContext, + coreSetup, + coreStart, + plugin, + setup, + doStart: async () => + await plugin.start(coreStart, { + inspector: inspectorPluginMock.createStartContract(), + }), + }; +}; + +export const expressionsPluginMock = { + createSetup: createExpressionsSetupMock, + createStart: createExpressionsStartMock, + createPlugin, +}; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts index 5a0eecc51ef19e..d2c6c14c17de1b 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts @@ -19,9 +19,10 @@ /* eslint-disable */ import { npSetup } from 'ui/new_platform'; -import { ExpressionsSetupContract } from '../../../../../../plugins/expressions/public/expressions/expressions_service'; /* eslint-enable */ +import { ExpressionsSetup } from '../../../../../../plugins/expressions/public'; + import { CoreSetup, CoreStart, @@ -32,9 +33,9 @@ import { Start as InspectorStart, Setup as InspectorSetup, } from '../../../../../../plugins/inspector/public'; -import { IInterpreter } from './types'; +import { ExpressionInterpreter } from './types'; import { setInterpreter, setInspector, setRenderersRegistry } from './services'; -import { createRenderer } from './expression_renderer'; +import { ExpressionRendererImplementation } from './expression_renderer'; import { ExpressionLoader, loader } from './loader'; import { ExpressionDataHandler, execute } from './execute'; import { ExpressionRenderHandler, render } from './render'; @@ -47,7 +48,7 @@ export interface ExpressionsStartDeps { inspector: InspectorStart; } -export type ExpressionsSetup = ExpressionsSetupContract; +export { ExpressionsSetup }; export type ExpressionsStart = ReturnType; export class ExpressionsPublicPlugin @@ -61,7 +62,7 @@ export class ExpressionsPublicPlugin // eslint-disable-next-line const { getInterpreter } = require('../../../../interpreter/public/interpreter'); getInterpreter() - .then(({ interpreter }: { interpreter: IInterpreter }) => { + .then(({ interpreter }: { interpreter: ExpressionInterpreter }) => { setInterpreter(interpreter); }) .catch((e: Error) => { @@ -77,17 +78,16 @@ export class ExpressionsPublicPlugin } public start(core: CoreStart, { inspector }: ExpressionsStartDeps) { - const ExpressionRenderer = createRenderer(loader); setInspector(inspector); return { execute, render, loader, + ExpressionRenderer: ExpressionRendererImplementation, ExpressionDataHandler, ExpressionRenderHandler, ExpressionLoader, - ExpressionRenderer, }; } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts index cf606b8fabec22..9d555f9760ee77 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts @@ -20,6 +20,8 @@ import { render, ExpressionRenderHandler } from './render'; import { Observable } from 'rxjs'; import { IInterpreterRenderHandlers } from './types'; +import { getRenderersRegistry } from './services'; +import { first } from 'rxjs/operators'; const element: HTMLElement = {} as HTMLElement; @@ -31,10 +33,11 @@ jest.mock('./services', () => { }, }, }; + return { - getRenderersRegistry: () => ({ - get: (id: string) => renderers[id], - }), + getRenderersRegistry: jest.fn(() => ({ + get: jest.fn((id: string) => renderers[id]), + })), }; }); @@ -46,8 +49,6 @@ describe('render helper function', () => { }); describe('ExpressionRenderHandler', () => { - const data = { type: 'render', as: 'test' }; - it('constructor creates observers', () => { const expressionRenderHandler = new ExpressionRenderHandler(element); expect(expressionRenderHandler.events$).toBeInstanceOf(Observable); @@ -61,27 +62,71 @@ describe('ExpressionRenderHandler', () => { }); describe('render()', () => { - it('throws if invalid data is provided', async () => { + it('sends an observable error and keeps it open if invalid data is provided', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - await expect(expressionRenderHandler.render({})).rejects.toThrow(); + const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render(false); + await expect(promise1).resolves.toEqual({ + type: 'error', + error: { + message: 'invalid data provided to the expression renderer', + }, + }); + + const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render(false); + await expect(promise2).resolves.toEqual({ + type: 'error', + error: { + message: 'invalid data provided to the expression renderer', + }, + }); }); - it('throws if renderer does not exist', async () => { + it('sends an observable error if renderer does not exist', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - await expect( - expressionRenderHandler.render({ type: 'render', as: 'something' }) - ).rejects.toThrow(); + const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render({ type: 'render', as: 'something' }); + await expect(promise).resolves.toEqual({ + type: 'error', + error: { + message: `invalid renderer id 'something'`, + }, + }); }); - it('returns a promise', () => { + it('sends an observable error if the rendering function throws', async () => { + (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ get: () => true }); + (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ + get: () => ({ + render: () => { + throw new Error('renderer error'); + }, + }), + }); + const expressionRenderHandler = new ExpressionRenderHandler(element); - expect(expressionRenderHandler.render(data)).toBeInstanceOf(Promise); + const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render({ type: 'render', as: 'something' }); + await expect(promise).resolves.toEqual({ + type: 'error', + error: { + message: 'renderer error', + }, + }); }); - it('resolves a promise once rendering is complete', async () => { + it('sends a next observable once rendering is complete', () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - const response = await expressionRenderHandler.render(data); - expect(response).toBe(1); + expect.assertions(1); + return new Promise(resolve => { + expressionRenderHandler.render$.subscribe(renderCount => { + expect(renderCount).toBe(1); + resolve(); + }); + + expressionRenderHandler.render({ type: 'render', as: 'test' }); + }); }); }); }); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts index 96f56a4b362024..8475325a2c6258 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts @@ -19,33 +19,41 @@ import { Observable } from 'rxjs'; import * as Rx from 'rxjs'; -import { share, first } from 'rxjs/operators'; +import { share } from 'rxjs/operators'; import { event, RenderId, Data, IInterpreterRenderHandlers } from './types'; import { getRenderersRegistry } from './services'; +interface RenderError { + type: 'error'; + error: { type?: string; message: string }; +} + export type IExpressionRendererExtraHandlers = Record; export class ExpressionRenderHandler { - render$: Observable; + render$: Observable; update$: Observable; events$: Observable; private element: HTMLElement; private destroyFn?: any; private renderCount: number = 0; + private renderSubject: Rx.Subject; + private eventsSubject: Rx.Subject; + private updateSubject: Rx.Subject; private handlers: IInterpreterRenderHandlers; constructor(element: HTMLElement) { this.element = element; - const eventsSubject = new Rx.Subject(); - this.events$ = eventsSubject.asObservable().pipe(share()); + this.eventsSubject = new Rx.Subject(); + this.events$ = this.eventsSubject.asObservable().pipe(share()); - const renderSubject = new Rx.Subject(); - this.render$ = renderSubject.asObservable().pipe(share()); + this.renderSubject = new Rx.Subject(); + this.render$ = this.renderSubject.asObservable().pipe(share()); - const updateSubject = new Rx.Subject(); - this.update$ = updateSubject.asObservable().pipe(share()); + this.updateSubject = new Rx.Subject(); + this.update$ = this.updateSubject.asObservable().pipe(share()); this.handlers = { onDestroy: (fn: any) => { @@ -53,39 +61,68 @@ export class ExpressionRenderHandler { }, done: () => { this.renderCount++; - renderSubject.next(this.renderCount); + this.renderSubject.next(this.renderCount); }, reload: () => { - updateSubject.next(null); + this.updateSubject.next(null); }, update: params => { - updateSubject.next(params); + this.updateSubject.next(params); }, event: data => { - eventsSubject.next(data); + this.eventsSubject.next(data); }, }; } - render = async (data: Data, extraHandlers: IExpressionRendererExtraHandlers = {}) => { - if (!data || data.type !== 'render' || !data.as) { - throw new Error('invalid data provided to expression renderer'); + render = (data: Data, extraHandlers: IExpressionRendererExtraHandlers = {}) => { + if (!data || typeof data !== 'object') { + this.renderSubject.next({ + type: 'error', + error: { + message: 'invalid data provided to the expression renderer', + }, + }); + return; } - if (!getRenderersRegistry().get(data.as)) { - throw new Error(`invalid renderer id '${data.as}'`); + if (data.type !== 'render' || !data.as) { + if (data.type === 'error') { + this.renderSubject.next(data); + } else { + this.renderSubject.next({ + type: 'error', + error: { message: 'invalid data provided to the expression renderer' }, + }); + } + return; } - const promise = this.render$.pipe(first()).toPromise(); - - getRenderersRegistry() - .get(data.as) - .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); + if (!getRenderersRegistry().get(data.as)) { + this.renderSubject.next({ + type: 'error', + error: { message: `invalid renderer id '${data.as}'` }, + }); + return; + } - return promise; + try { + // Rendering is asynchronous, completed by handlers.done() + getRenderersRegistry() + .get(data.as)! + .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); + } catch (e) { + this.renderSubject.next({ + type: 'error', + error: { type: e.type, message: e.message }, + }); + } }; destroy = () => { + this.renderSubject.complete(); + this.eventsSubject.complete(); + this.updateSubject.complete(); if (this.destroyFn) { this.destroyFn(); } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts index 4d95a8a91d0bb6..5c357b5dcd2bb7 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts @@ -17,27 +17,15 @@ * under the License. */ -import { IInterpreter } from './types'; +import { ExpressionInterpreter } from './types'; +import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; import { Start as IInspector } from '../../../../../../plugins/inspector/public'; import { ExpressionsSetup } from './plugin'; -const createGetterSetter = (name: string): [() => T, (value: T) => void] => { - let value: T; - - const get = (): T => { - if (!value) throw new Error(`${name} was not set`); - return value; - }; - - const set = (newValue: T) => { - value = newValue; - }; - - return [get, set]; -}; - export const [getInspector, setInspector] = createGetterSetter('Inspector'); -export const [getInterpreter, setInterpreter] = createGetterSetter('Interpreter'); +export const [getInterpreter, setInterpreter] = createGetterSetter( + 'Interpreter' +); export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< ExpressionsSetup['__LEGACY']['renderers'] >('Renderers registry'); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts index 09aaa363c94928..9d7b4fb6d04806 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts @@ -17,71 +17,9 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { TimeRange } from '../../../../../../plugins/data/public'; import { Adapters } from '../../../../../../plugins/inspector/public'; import { Query } from '../../../../../../plugins/data/public'; -import { ExpressionAST } from '../../../../../../plugins/expressions/common'; -export { ExpressionAST, TimeRange, Adapters, Filter, Query }; - -export type RenderId = number; -export type Data = any; -export type event = any; -export type Context = object; - -export interface SearchContext { - type: 'kibana_context'; - filters?: Filter[]; - query?: Query; - timeRange?: TimeRange; -} - -export type IGetInitialContext = () => SearchContext | Context; - -export interface IExpressionLoaderParams { - searchContext?: SearchContext; - context?: Context; - variables?: Record; - disableCaching?: boolean; - customFunctions?: []; - customRenderers?: []; - extraHandlers?: Record; -} - -export interface IInterpreterHandlers { - getInitialContext: IGetInitialContext; - inspectorAdapters?: Adapters; -} - -export interface IInterpreterResult { - type: string; - as?: string; - value?: unknown; - error?: unknown; -} - -export interface IInterpreterRenderHandlers { - done: () => void; - onDestroy: (fn: () => void) => void; - reload: () => void; - update: (params: any) => void; - event: (event: event) => void; -} - -export interface IInterpreterRenderFunction { - name: string; - displayName: string; - help: string; - validate: () => void; - reuseDomNode: boolean; - render: (domNode: Element, data: T, handlers: IInterpreterRenderHandlers) => void | Promise; -} - -export interface IInterpreter { - interpretAst( - ast: ExpressionAST, - context: Context, - handlers: IInterpreterHandlers - ): Promise; -} +export { TimeRange, Adapters, Query }; +export * from '../../../../../../plugins/expressions/public'; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js index 9f0ed1dfb5097b..65b1d41fa82393 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js @@ -20,12 +20,8 @@ import _ from 'lodash'; import { FilterManager } from './filter_manager.js'; import { - buildPhraseFilter, - buildPhrasesFilter, - getPhraseFilterField, - getPhraseFilterValue, - isPhraseFilter, -} from '@kbn/es-query'; + esFilters, +} from '../../../../../../plugins/data/public'; export class PhraseFilterManager extends FilterManager { constructor(controlId, fieldName, indexPattern, queryFilter) { @@ -43,12 +39,12 @@ export class PhraseFilterManager extends FilterManager { createFilter(phrases) { let newFilter; if (phrases.length === 1) { - newFilter = buildPhraseFilter( + newFilter = esFilters.buildPhraseFilter( this.indexPattern.fields.getByName(this.fieldName), phrases[0], this.indexPattern); } else { - newFilter = buildPhrasesFilter( + newFilter = esFilters.buildPhrasesFilter( this.indexPattern.fields.getByName(this.fieldName), phrases, this.indexPattern); @@ -107,12 +103,12 @@ export class PhraseFilterManager extends FilterManager { } // single phrase filter - if (isPhraseFilter(kbnFilter)) { - if (getPhraseFilterField(kbnFilter) !== this.fieldName) { + if (esFilters.isPhraseFilter(kbnFilter)) { + if (esFilters.getPhraseFilterField(kbnFilter) !== this.fieldName) { return; } - return getPhraseFilterValue(kbnFilter); + return esFilters.getPhraseFilterValue(kbnFilter); } // single phrase filter from bool filter diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js index 1c8f5e2aa5a3e3..3a232fd8b543d7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import { FilterManager } from './filter_manager.js'; -import { buildRangeFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; // Convert slider value into ES range filter function toRange(sliderValue) { @@ -55,7 +55,7 @@ export class RangeFilterManager extends FilterManager { * @return {object} range filter */ createFilter(value) { - const newFilter = buildRangeFilter( + const newFilter = esFilters.buildRangeFilter( this.indexPattern.fields.getByName(this.fieldName), toRange(value), this.indexPattern); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js index 44c68c84579c6f..86fe6db9b0778e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js @@ -26,6 +26,7 @@ import { import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; import { createSearchSource } from './create_search_source'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; import { start as data } from '../../../../core_plugins/data/public/legacy'; @@ -187,9 +188,10 @@ export async function listControlFactory(controlParams, useTimeFilter, SearchSou // ignore not found error and return control so it can be displayed in disabled state. } + const { filterManager } = npStart.plugins.data.query; return new ListControl( controlParams, - new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, data.filter.filterManager), + new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager), useTimeFilter, SearchSource, ); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js index d5c23c2c1c8551..b40a9f8e6efd40 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js @@ -24,6 +24,28 @@ jest.mock('ui/timefilter', () => ({ createFilter: jest.fn(), })); +jest.mock('ui/new_platform', () => ({ + npStart: { + plugins: { + data: { + query: { + filterManager: { + fieldName: 'myNumberField', + getIndexPattern: () => ({ + fields: { getByName: name => { + const fields = { myField: { name: 'myField' } }; + return fields[name]; + } } + }), + getAppFilters: jest.fn().mockImplementation(() => ([])), + getGlobalFilters: jest.fn().mockImplementation(() => ([])), + } + } + } + }, + }, +})); + jest.mock('../../../../core_plugins/data/public/legacy', () => ({ start: { indexPatterns: { @@ -36,19 +58,6 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({ }), } }, - filter: { - filterManager: { - fieldName: 'myNumberField', - getIndexPattern: () => ({ - fields: { getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - } } - }), - getAppFilters: jest.fn().mockImplementation(() => ([])), - getGlobalFilters: jest.fn().mockImplementation(() => ([])), - } - } } })); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js index efb208bd800453..2a05a1224aab98 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js @@ -27,6 +27,7 @@ import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; import { i18n } from '@kbn/i18n'; import { start as data } from '../../../../core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; const minMaxAgg = (field) => { const aggBody = {}; @@ -106,9 +107,10 @@ export async function rangeControlFactory(controlParams, useTimeFilter, SearchSo } catch (err) { // ignore not found error and return control so it can be displayed in disabled state. } + const { filterManager } = npStart.plugins.data.query; return new RangeControl( controlParams, - new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, data.filter.filterManager), + new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager), useTimeFilter, SearchSource, ); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js index c746d116c70b21..3e6d6a49a11184 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js @@ -32,6 +32,28 @@ jest.mock('ui/timefilter', () => ({ createFilter: jest.fn(), })); +jest.mock('ui/new_platform', () => ({ + npStart: { + plugins: { + data: { + query: { + filterManager: { + fieldName: 'myNumberField', + getIndexPattern: () => ({ + fields: { getByName: name => { + const fields = { myNumberField: { name: 'myNumberField' } }; + return fields[name]; + } + } }), + getAppFilters: jest.fn().mockImplementation(() => ([])), + getGlobalFilters: jest.fn().mockImplementation(() => ([])), + } + } + } + }, + }, +})); + jest.mock('../../../../core_plugins/data/public/legacy', () => ({ start: { indexPatterns: { @@ -44,19 +66,6 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({ } }), } }, - filter: { - filterManager: { - fieldName: 'myNumberField', - getIndexPattern: () => ({ - fields: { getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - } - } }), - getAppFilters: jest.fn().mockImplementation(() => ([])), - getGlobalFilters: jest.fn().mockImplementation(() => ([])), - } - } } })); diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js index 1edf5652a76c53..792ff3fe85479f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js +++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js @@ -23,7 +23,7 @@ import { I18nContext } from 'ui/i18n'; import { InputControlVis } from './components/vis/input_control_vis'; import { controlFactory } from './control/control_factory'; import { getLineageMap } from './lineage'; -import { start as data } from '../../../core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { SearchSource } from '../../../ui/public/courier/search_source/search_source'; class VisController { @@ -34,7 +34,7 @@ class VisController { this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); - this.filterManager = data.filter.filterManager; + this.filterManager = npStart.plugins.data.query.filterManager; this.updateSubsciption = this.filterManager.getUpdates$() .subscribe(this.queryBarUpdateHandler); } diff --git a/src/legacy/core_plugins/interpreter/common/index.ts b/src/legacy/core_plugins/interpreter/common/index.ts index 0251faa69fccf3..8a54741e9d3e1b 100644 --- a/src/legacy/core_plugins/interpreter/common/index.ts +++ b/src/legacy/core_plugins/interpreter/common/index.ts @@ -36,7 +36,8 @@ export { PointSeriesColumn, PointSeriesColumnName, Render, + ExpressionTypeStyle, Style, Type, -} from '../../../../plugins/expressions/common'; +} from '../../../../plugins/expressions/public'; export const API_ROUTE = '/api/interpreter'; diff --git a/src/legacy/core_plugins/interpreter/common/lib/fonts.ts b/src/legacy/core_plugins/interpreter/common/lib/fonts.ts index cdf3d4c16f3b57..1594f42abf2eba 100644 --- a/src/legacy/core_plugins/interpreter/common/lib/fonts.ts +++ b/src/legacy/core_plugins/interpreter/common/lib/fonts.ts @@ -17,135 +17,5 @@ * under the License. */ -/** - * This type contains a unions of all supported font labels, or the the name of - * the font the user would see in a UI. - */ -export type FontLabel = typeof fonts[number]['label']; - -/** - * This type contains a union of all supported font values, equivalent to the CSS - * `font-value` property. - */ -export type FontValue = typeof fonts[number]['value']; - -/** - * An interface representing a font in Canvas, with a textual label and the CSS - * `font-value`. - */ -export interface Font { - label: FontLabel; - value: FontValue; -} - -// This function allows one to create a strongly-typed font for inclusion in -// the font collection. As a result, the values and labels are known to the -// type system, preventing one from specifying a non-existent font at build -// time. -function createFont< - RawFont extends { value: RawFontValue; label: RawFontLabel }, - RawFontValue extends string, - RawFontLabel extends string ->(font: RawFont) { - return font; -} - -export const americanTypewriter = createFont({ - label: 'American Typewriter', - value: "'American Typewriter', 'Courier New', Courier, Monaco, mono", -}); - -export const arial = createFont({ label: 'Arial', value: 'Arial, sans-serif' }); - -export const baskerville = createFont({ - label: 'Baskerville', - value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const bookAntiqua = createFont({ - label: 'Book Antiqua', - value: "'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const brushScript = createFont({ - label: 'Brush Script', - value: "'Brush Script MT', 'Comic Sans', sans-serif", -}); - -export const chalkboard = createFont({ - label: 'Chalkboard', - value: "Chalkboard, 'Comic Sans', sans-serif", -}); - -export const didot = createFont({ - label: 'Didot', - value: "Didot, Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const futura = createFont({ - label: 'Futura', - value: 'Futura, Impact, Helvetica, Arial, sans-serif', -}); - -export const gillSans = createFont({ - label: 'Gill Sans', - value: - "'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", -}); - -export const helveticaNeue = createFont({ - label: 'Helvetica Neue', - value: "'Helvetica Neue', Helvetica, Arial, sans-serif", -}); - -export const hoeflerText = createFont({ - label: 'Hoefler Text', - value: "'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif", -}); - -export const lucidaGrande = createFont({ - label: 'Lucida Grande', - value: "'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif", -}); - -export const myriad = createFont({ - label: 'Myriad', - value: 'Myriad, Helvetica, Arial, sans-serif', -}); - -export const openSans = createFont({ - label: 'Open Sans', - value: "'Open Sans', Helvetica, Arial, sans-serif", -}); - -export const optima = createFont({ - label: 'Optima', - value: "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", -}); - -export const palatino = createFont({ - label: 'Palatino', - value: "Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -/** - * A collection of supported fonts. - */ -export const fonts = [ - americanTypewriter, - arial, - baskerville, - bookAntiqua, - brushScript, - chalkboard, - didot, - futura, - gillSans, - helveticaNeue, - hoeflerText, - lucidaGrande, - myriad, - openSans, - optima, - palatino, -]; +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/fonts'; diff --git a/src/legacy/core_plugins/interpreter/init.ts b/src/legacy/core_plugins/interpreter/init.ts index fc93346f280249..f09ffd4697e74e 100644 --- a/src/legacy/core_plugins/interpreter/init.ts +++ b/src/legacy/core_plugins/interpreter/init.ts @@ -25,7 +25,7 @@ import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common // @ts-ignore import { routes } from './server/routes'; -import { typeSpecs as types } from '../../../plugins/expressions/common'; +import { typeSpecs as types } from '../../../plugins/expressions/public'; import { Type } from './common'; import { Legacy } from '../../../../kibana'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/clog.ts b/src/legacy/core_plugins/interpreter/public/functions/clog.ts index 586584b498ac72..65362fe3633745 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/clog.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/clog.ts @@ -17,19 +17,5 @@ * under the License. */ -import { ExpressionFunction } from '../../types'; - -const name = 'clog'; - -type Context = any; -type ClogExpressionFunction = ExpressionFunction; - -export const clog = (): ClogExpressionFunction => ({ - name, - args: {}, - help: 'Outputs the context to the console', - fn: context => { - console.log(context); // eslint-disable-line no-console - return context; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/functions/clog'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index 6fcfde0a5b06b5..d232a97c3c34c4 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -19,18 +19,12 @@ import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -// @ts-ignore import { AggConfigs } from 'ui/agg_types/agg_configs'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import chrome from 'ui/chrome'; - -// need to get rid of angular from these -// @ts-ignore import { TimeRange } from 'src/plugins/data/public'; import { SearchSource } from '../../../../ui/public/courier/search_source'; -// @ts-ignore import { FilterBarQueryFilterProvider } from '../../../../ui/public/filter_manager/query_filter'; - import { buildTabularInspectorData } from '../../../../ui/public/inspector/build_tabular_inspector_data'; import { getRequestInspectorStats, @@ -39,12 +33,15 @@ import { import { calculateObjectHash } from '../../../../ui/public/vis/lib/calculate_object_hash'; import { getTime } from '../../../../ui/public/timefilter'; import { RequestHandlerParams } from '../../../../ui/public/visualize/loader/embedded_visualize_handler'; -// @ts-ignore -import { tabifyAggResponse } from '../../../../ui/public/agg_response/tabify/tabify'; import { KibanaContext, KibanaDatatable } from '../../common'; import { ExpressionFunction, KibanaDatatableColumn } from '../../types'; import { start as data } from '../../../data/public/legacy'; +// @ts-ignore +import { tabifyAggResponse } from '../../../../ui/public/agg_response/tabify/tabify'; +// @ts-ignore +import { SearchSourceProvider } from '../../../../ui/public/courier/search_source'; + const name = 'esaggs'; type Context = KibanaContext | null; diff --git a/src/legacy/core_plugins/interpreter/public/functions/font.ts b/src/legacy/core_plugins/interpreter/public/functions/font.ts index 5cd5add318e26c..2f3bc1ff37e2c4 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/font.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/font.ts @@ -17,161 +17,5 @@ * under the License. */ -// @ts-ignore no @typed def -import inlineStyle from 'inline-style'; -import { i18n } from '@kbn/i18n'; -import { openSans } from '../../common/lib/fonts'; -import { ExpressionFunction } from '../../types'; -import { - CSSStyle, - FontFamily, - FontStyle, - FontWeight, - Style, - TextAlignment, - TextDecoration, -} from '../types'; - -interface Arguments { - align?: TextAlignment; - color?: string; - family?: FontFamily; - italic?: boolean; - lHeight?: number | null; - size?: number; - underline?: boolean; - weight?: FontWeight; -} - -export function font(): ExpressionFunction<'font', null, Arguments, Style> { - return { - name: 'font', - aliases: [], - type: 'style', - help: i18n.translate('interpreter.functions.fontHelpText', { - defaultMessage: 'Create a font style.', - }), - context: { - types: ['null'], - }, - args: { - align: { - default: 'left', - help: i18n.translate('interpreter.functions.font.args.alignHelpText', { - defaultMessage: 'The horizontal text alignment.', - }), - options: Object.values(TextAlignment), - types: ['string'], - }, - color: { - help: i18n.translate('interpreter.functions.font.args.colorHelpText', { - defaultMessage: 'The text color.', - }), - types: ['string'], - }, - family: { - default: `"${openSans.value}"`, - help: i18n.translate('interpreter.functions.font.args.familyHelpText', { - defaultMessage: 'An acceptable {css} web font string', - values: { - css: 'CSS', - }, - }), - types: ['string'], - }, - italic: { - default: false, - help: i18n.translate('interpreter.functions.font.args.italicHelpText', { - defaultMessage: 'Italicize the text?', - }), - options: [true, false], - types: ['boolean'], - }, - lHeight: { - default: null, - aliases: ['lineHeight'], - help: i18n.translate('interpreter.functions.font.args.lHeightHelpText', { - defaultMessage: 'The line height in pixels', - }), - types: ['number', 'null'], - }, - size: { - default: 14, - help: i18n.translate('interpreter.functions.font.args.sizeHelpText', { - defaultMessage: 'The font size in pixels', - }), - types: ['number'], - }, - underline: { - default: false, - help: i18n.translate('interpreter.functions.font.args.underlineHelpText', { - defaultMessage: 'Underline the text?', - }), - options: [true, false], - types: ['boolean'], - }, - weight: { - default: 'normal', - help: i18n.translate('interpreter.functions.font.args.weightHelpText', { - defaultMessage: 'The font weight. For example, {list}, or {end}.', - values: { - list: Object.values(FontWeight) - .slice(0, -1) - .map(weight => `\`"${weight}"\``) - .join(', '), - end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, - }, - }), - options: Object.values(FontWeight), - types: ['string'], - }, - }, - fn: (_context, args) => { - if (!Object.values(FontWeight).includes(args.weight!)) { - throw new Error( - i18n.translate('interpreter.functions.font.invalidFontWeightErrorMessage', { - defaultMessage: "Invalid font weight: '{weight}'", - values: { - weight: args.weight, - }, - }) - ); - } - if (!Object.values(TextAlignment).includes(args.align!)) { - throw new Error( - i18n.translate('interpreter.functions.font.invalidTextAlignmentErrorMessage', { - defaultMessage: "Invalid text alignment: '{align}'", - values: { - align: args.align, - }, - }) - ); - } - - // the line height shouldn't ever be lower than the size, and apply as a - // pixel setting - const lineHeight = args.lHeight != null ? `${args.lHeight}px` : '1'; - - const spec: CSSStyle = { - fontFamily: args.family, - fontWeight: args.weight, - fontStyle: args.italic ? FontStyle.ITALIC : FontStyle.NORMAL, - textDecoration: args.underline ? TextDecoration.UNDERLINE : TextDecoration.NONE, - textAlign: args.align, - fontSize: `${args.size}px`, // apply font size as a pixel setting - lineHeight, // apply line height as a pixel setting - }; - - // conditionally apply styles based on input - if (args.color) { - spec.color = args.color; - } - - return { - type: 'style', - spec, - css: inlineStyle(spec), - }; - }, - }; -} +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/functions/font'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/index.ts b/src/legacy/core_plugins/interpreter/public/functions/index.ts deleted file mode 100644 index d86f033acb3d13..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/index.ts +++ /dev/null @@ -1,38 +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 { clog } from './clog'; -import { esaggs } from './esaggs'; -import { font } from './font'; -import { kibana } from './kibana'; -import { kibanaContext } from './kibana_context'; -import { range } from './range'; -import { visualization } from './visualization'; -import { visDimension } from './vis_dimension'; - -export const functions = [ - clog, - esaggs, - font, - kibana, - kibanaContext, - range, - visualization, - visDimension, -]; diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana.ts b/src/legacy/core_plugins/interpreter/public/functions/kibana.ts index c027b220ad0d0c..7da284d9672a55 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/kibana.ts @@ -17,47 +17,5 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext } from '../../types'; - -export type ExpressionFunctionKibana = ExpressionFunction< - 'kibana', - KibanaContext | null, - object, - KibanaContext ->; - -export const kibana = (): ExpressionFunctionKibana => ({ - name: 'kibana', - type: 'kibana_context', - - context: { - types: ['kibana_context', 'null'], - }, - - help: i18n.translate('interpreter.functions.kibana.help', { - defaultMessage: 'Gets kibana global context', - }), - args: {}, - fn(context, args, handlers) { - const initialContext = handlers.getInitialContext ? handlers.getInitialContext() : {}; - - if (context && context.query) { - initialContext.query = initialContext.query.concat(context.query); - } - - if (context && context.filters) { - initialContext.filters = initialContext.filters.concat(context.filters); - } - - const timeRange = initialContext.timeRange || (context ? context.timeRange : undefined); - - return { - ...context, - type: 'kibana_context', - query: initialContext.query, - filters: initialContext.filters, - timeRange, - }; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/functions/kibana'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana_context.ts b/src/legacy/core_plugins/interpreter/public/functions/kibana_context.ts deleted file mode 100644 index 1ba7b450c5409a..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana_context.ts +++ /dev/null @@ -1,114 +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 chrome from 'ui/chrome'; -import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext } from '../../types'; - -interface Arguments { - q?: string | null; - filters?: string | null; - timeRange?: string | null; - savedSearchId?: string | null; -} - -export type ExpressionFunctionKibanaContext = ExpressionFunction< - 'kibana_context', - KibanaContext | null, - Arguments, - Promise ->; - -export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ - name: 'kibana_context', - type: 'kibana_context', - context: { - types: ['kibana_context', 'null'], - }, - help: i18n.translate('interpreter.functions.kibana_context.help', { - defaultMessage: 'Updates kibana global context', - }), - args: { - q: { - types: ['string', 'null'], - aliases: ['query', '_'], - default: null, - help: i18n.translate('interpreter.functions.kibana_context.q.help', { - defaultMessage: 'Specify Kibana free form text query', - }), - }, - filters: { - types: ['string', 'null'], - default: '"[]"', - help: i18n.translate('interpreter.functions.kibana_context.filters.help', { - defaultMessage: 'Specify Kibana generic filters', - }), - }, - timeRange: { - types: ['string', 'null'], - default: null, - help: i18n.translate('interpreter.functions.kibana_context.timeRange.help', { - defaultMessage: 'Specify Kibana time range filter', - }), - }, - savedSearchId: { - types: ['string', 'null'], - default: null, - help: i18n.translate('interpreter.functions.kibana_context.savedSearchId.help', { - defaultMessage: 'Specify saved search ID to be used for queries and filters', - }), - }, - }, - async fn(context, args, handlers) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const savedSearches = $injector.get('savedSearches') as any; - const queryArg = args.q ? JSON.parse(args.q) : []; - let queries = Array.isArray(queryArg) ? queryArg : [queryArg]; - let filters = args.filters ? JSON.parse(args.filters) : []; - - if (args.savedSearchId) { - const savedSearch = await savedSearches.get(args.savedSearchId); - const searchQuery = savedSearch.searchSource.getField('query'); - const searchFilters = savedSearch.searchSource.getField('filter'); - queries = queries.concat(searchQuery); - filters = filters.concat(searchFilters); - } - - if (context && context.query) { - queries = queries.concat(context.query); - } - - if (context && context.filters) { - filters = filters.concat(context.filters).filter((f: any) => !f.meta.disabled); - } - - const timeRange = args.timeRange - ? JSON.parse(args.timeRange) - : context - ? context.timeRange - : undefined; - - return { - type: 'kibana_context', - query: queries, - filters, - timeRange, - }; - }, -}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/range.ts b/src/legacy/core_plugins/interpreter/public/functions/range.ts index 8ae07dd82a22c0..322bda983a8ff8 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/range.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/range.ts @@ -17,48 +17,5 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { KibanaDatatable, Range } from '../../types'; -import { ExpressionFunction } from '../../types'; - -const name = 'range'; - -type Context = KibanaDatatable | null; - -interface Arguments { - from: number; - to: number; -} - -type Return = Range; // imported from type - -export const range = (): ExpressionFunction => ({ - name, - help: i18n.translate('interpreter.function.range.help', { - defaultMessage: 'Generates range object', - }), - type: 'range', - args: { - from: { - types: ['number'], - help: i18n.translate('interpreter.function.range.from.help', { - defaultMessage: 'Start of range', - }), - required: true, - }, - to: { - types: ['number'], - help: i18n.translate('interpreter.function.range.to.help', { - defaultMessage: 'End of range', - }), - required: true, - }, - }, - fn: (context, args) => { - return { - type: 'range', - from: args.from, - to: args.to, - }; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/visualizations/public/expression_functions/range'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts b/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts index 4190a597b0120f..d4ebeeb5267e1b 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts @@ -17,73 +17,5 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaDatatable } from '../../types'; - -const name = 'visdimension'; - -type Context = KibanaDatatable | null; - -interface Arguments { - accessor: string | number; - format?: string; - formatParams?: string; -} - -type Return = any; - -export const visDimension = (): ExpressionFunction => ({ - name: 'visdimension', - help: i18n.translate('interpreter.function.visDimension.help', { - defaultMessage: 'Generates visConfig dimension object', - }), - type: 'vis_dimension', - context: { - types: ['kibana_datatable'], - }, - args: { - accessor: { - types: ['string', 'number'], - aliases: ['_'], - help: i18n.translate('interpreter.function.visDimension.accessor.help', { - defaultMessage: 'Column in your dataset to use (either column index or column name)', - }), - }, - format: { - types: ['string'], - default: 'string', - help: i18n.translate('interpreter.function.visDimension.format.help', { - defaultMessage: 'Format', - }), - }, - formatParams: { - types: ['string'], - default: '"{}"', - help: i18n.translate('interpreter.function.visDimension.formatParams.help', { - defaultMessage: 'Format params', - }), - }, - }, - fn: (context, args) => { - const accessor = - typeof args.accessor === 'number' - ? args.accessor - : context!.columns.find(c => c.id === args.accessor); - if (accessor === undefined) { - throw new Error( - i18n.translate('interpreter.function.visDimension.error.accessor', { - defaultMessage: 'Column name provided is invalid', - }) - ); - } - - return { - type: 'vis_dimension', - accessor, - format: { - id: args.format, - params: JSON.parse(args.formatParams!), - }, - }; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/visualizations/public/expression_functions/vis_dimension'; diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.test.ts b/src/legacy/core_plugins/interpreter/public/interpreter.test.ts deleted file mode 100644 index 429a943c3ff362..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/interpreter.test.ts +++ /dev/null @@ -1,120 +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. - */ - -jest.mock('ui/new_platform', () => ({ - npSetup: { - core: { - http: {}, - injectedMetadata: { - getKibanaVersion: () => '8.0.0', - getBasePath: () => '/lol', - }, - }, - }, -})); -jest.mock('uiExports/interpreter'); - -jest.mock('@kbn/interpreter/common', () => ({ - register: jest.fn(), - registryFactory: jest.fn(), -})); - -const mockExecutor = { - interpreter: { - interpretAst: jest.fn(), - }, -}; -jest.mock('./lib/interpreter', () => ({ - initializeExecutor: jest.fn().mockReturnValue(Promise.resolve(mockExecutor)), -})); - -jest.mock('./registries', () => ({ - registries: { - browserFunctions: jest.fn(), - renderers: jest.fn(), - types: jest.fn(), - }, -})); - -jest.mock('../../../ui/public/new_platform'); -jest.mock('./functions', () => ({ functions: [{}, {}, {}] })); -jest.mock('./renderers/visualization', () => ({ visualization: {} })); - -describe('interpreter/interpreter', () => { - let getInterpreter: any; - let interpretAst: any; - let initializeExecutor: any; - - beforeEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - getInterpreter = require('./interpreter').getInterpreter; - interpretAst = require('./interpreter').interpretAst; - initializeExecutor = require('./lib/interpreter').initializeExecutor; - }); - - describe('getInterpreter', () => { - it('initializes interpreter', async () => { - await getInterpreter(); - expect(initializeExecutor).toHaveBeenCalledTimes(1); - }); - - it('only initializes interpreter once', async () => { - await getInterpreter(); - await getInterpreter(); - expect(initializeExecutor).toHaveBeenCalledTimes(1); - }); - - it('resolves', async () => { - await expect(getInterpreter()).resolves; - }); - - it('resolves with interpreter object', async () => { - const interpreter = await getInterpreter(); - await expect(Object.keys(interpreter)).toEqual(['interpreter']); - }); - }); - - describe('interpretAst', () => { - it('resolves', async () => { - const params = [{}]; - await expect(interpretAst(...params)).resolves; - }); - - it('initializes interpreter if needed', async () => { - const params = [{}]; - await interpretAst(...params); - expect(initializeExecutor).toHaveBeenCalledTimes(1); - }); - - it('calls interpreter.interpretAst with the provided params', async () => { - const params = [{}]; - await interpretAst(...params); - expect(mockExecutor.interpreter.interpretAst).toHaveBeenCalledTimes(1); - expect(mockExecutor.interpreter.interpretAst).toHaveBeenCalledWith({}, undefined, undefined); - }); - - it('calls interpreter.interpretAst each time', async () => { - const params = [{}]; - await interpretAst(...params); - await interpretAst(...params); - expect(mockExecutor.interpreter.interpretAst).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.ts b/src/legacy/core_plugins/interpreter/public/interpreter.ts index 1f0f8141345d20..a337f7e4ebfea2 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.ts +++ b/src/legacy/core_plugins/interpreter/public/interpreter.ts @@ -20,38 +20,42 @@ import 'uiExports/interpreter'; // @ts-ignore import { register, registryFactory } from '@kbn/interpreter/common'; -import { - initializeExecutor, - ExpressionExecutor, - ExpressionInterpretWithHandlers, -} from './lib/interpreter'; +import { npSetup } from 'ui/new_platform'; import { registries } from './registries'; -import { functions } from './functions'; import { visualization } from './renderers/visualization'; -import { typeSpecs } from '../../../../plugins/expressions/common'; +import { + ExpressionInterpretWithHandlers, + ExpressionExecutor, +} from '../../../../plugins/expressions/public'; +import { esaggs as esaggsFn } from './functions/esaggs'; +import { visualization as visualizationFn } from './functions/visualization'; // Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins // can register without a transpile step. +// TODO: This will be left behind in then legacy platform? (global as any).kbnInterpreter = Object.assign( (global as any).kbnInterpreter || {}, registryFactory(registries) ); -register(registries, { - types: typeSpecs, - browserFunctions: functions, - renderers: [visualization], -}); +// TODO: This needs to be moved to `data` plugin Search service. +registries.browserFunctions.register(esaggsFn); -let executorPromise: Promise | undefined; +// TODO: This needs to be moved to `visualizations` plugin. +registries.browserFunctions.register(visualizationFn); +registries.renderers.register(visualization); +// TODO: This function will be left behind in the legacy platform. +let executorPromise: Promise | undefined; export const getInterpreter = async () => { if (!executorPromise) { - executorPromise = initializeExecutor(); + const executor = npSetup.plugins.expressions.__LEGACY.getExecutor(); + executorPromise = Promise.resolve(executor); } return await executorPromise; }; +// TODO: This function will be left behind in the legacy platform. export const interpretAst: ExpressionInterpretWithHandlers = async (ast, context, handlers) => { const { interpreter } = await getInterpreter(); return await interpreter.interpretAst(ast, context, handlers); diff --git a/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts b/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts index 46e85411c58956..c14272fbf8def5 100644 --- a/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts +++ b/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts @@ -17,8 +17,5 @@ * under the License. */ -export function createHandlers() { - return { - environment: 'client', - }; -} +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/create_handlers'; diff --git a/src/legacy/core_plugins/interpreter/public/lib/interpreter.ts b/src/legacy/core_plugins/interpreter/public/lib/interpreter.ts deleted file mode 100644 index 399ecc59502681..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/lib/interpreter.ts +++ /dev/null @@ -1,51 +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 { ExpressionInterpret } from 'src/plugins/expressions/common/expressions/interpreter_provider'; -import { interpreterProvider } from '../../common'; -import { createHandlers } from './create_handlers'; -import { registries } from '../registries'; -import { FunctionHandlers } from '../../types'; - -export type ExpressionInterpretWithHandlers = ( - ast: Parameters[0], - context: Parameters[1], - handlers: FunctionHandlers -) => ReturnType; - -export interface ExpressionInterpreter { - interpretAst: ExpressionInterpretWithHandlers; -} - -export interface ExpressionExecutor { - interpreter: ExpressionInterpreter; -} - -export async function initializeExecutor(): Promise { - const interpretAst: ExpressionInterpretWithHandlers = async (ast, context, handlers) => { - const interpret = await interpreterProvider({ - types: registries.types.toJS(), - handlers: { ...handlers, ...createHandlers() }, - functions: registries.browserFunctions.toJS(), - }); - return interpret(ast, context); - }; - - return { interpreter: { interpretAst } }; -} diff --git a/src/legacy/core_plugins/interpreter/public/renderers/visualization.ts b/src/legacy/core_plugins/interpreter/public/renderers/visualization.ts index 90020f819dbe24..bedba6bfacedec 100644 --- a/src/legacy/core_plugins/interpreter/public/renderers/visualization.ts +++ b/src/legacy/core_plugins/interpreter/public/renderers/visualization.ts @@ -38,7 +38,11 @@ export const visualization = () => ({ // special case in visualize, we need to render first (without executing the expression), for maps to work if (visConfig) { $rootScope.$apply(() => { - handlers.vis.setCurrentState({ type: visType, params: visConfig }); + handlers.vis.setCurrentState({ + type: visType, + params: visConfig, + title: handlers.vis.title, + }); }); } } else { diff --git a/src/legacy/core_plugins/interpreter/public/types/style.ts b/src/legacy/core_plugins/interpreter/public/types/style.ts index fd22b9bae2e950..7386048a4ae042 100644 --- a/src/legacy/core_plugins/interpreter/public/types/style.ts +++ b/src/legacy/core_plugins/interpreter/public/types/style.ts @@ -17,120 +17,10 @@ * under the License. */ -import { FontLabel } from '../../common/lib/fonts'; -export { FontLabel as FontFamily, FontValue } from '../../common/lib/fonts'; - -/** - * Enum of supported CSS `background-repeat` properties. - */ -export enum BackgroundRepeat { - REPEAT = 'repeat', - REPEAT_NO = 'no-repeat', - REPEAT_X = 'repeat-x', - REPEAT_Y = 'repeat-y', - ROUND = 'round', - SPACE = 'space', -} - -/** - * Enum of supported CSS `background-size` properties. - */ -export enum BackgroundSize { - AUTO = 'auto', - CONTAIN = 'contain', - COVER = 'cover', -} - -/** - * Enum of supported CSS `font-style` properties. - */ -export enum FontStyle { - ITALIC = 'italic', - NORMAL = 'normal', -} - -/** - * Enum of supported CSS `font-weight` properties. - */ -export enum FontWeight { - NORMAL = 'normal', - BOLD = 'bold', - BOLDER = 'bolder', - LIGHTER = 'lighter', - ONE = '100', - TWO = '200', - THREE = '300', - FOUR = '400', - FIVE = '500', - SIX = '600', - SEVEN = '700', - EIGHT = '800', - NINE = '900', -} - -/** - * Enum of supported CSS `overflow` properties. - */ -export enum Overflow { - AUTO = 'auto', - HIDDEN = 'hidden', - SCROLL = 'scroll', - VISIBLE = 'visible', -} - -/** - * Enum of supported CSS `text-align` properties. - */ -export enum TextAlignment { - CENTER = 'center', - JUSTIFY = 'justify', - LEFT = 'left', - RIGHT = 'right', -} - -/** - * Enum of supported CSS `text-decoration` properties. - */ -export enum TextDecoration { - NONE = 'none', - UNDERLINE = 'underline', -} - -/** - * Represents the various style properties that can be applied to an element. - */ -export interface CSSStyle { - color?: string; - fill?: string; - fontFamily?: FontLabel; - fontSize?: string; - fontStyle?: FontStyle; - fontWeight?: FontWeight; - lineHeight?: number | string; - textAlign?: TextAlignment; - textDecoration?: TextDecoration; -} - -/** - * Represents an object containing style information for a Container. - */ -export interface ContainerStyle { - border: string | null; - borderRadius: string | null; - padding: string | null; - backgroundColor: string | null; - backgroundImage: string | null; - backgroundSize: BackgroundSize; - backgroundRepeat: BackgroundRepeat; - opacity: number | null; - overflow: Overflow; -} - -/** - * An object that represents style information, typically CSS. - */ -export interface Style { - type: 'style'; - spec: CSSStyle; - css: string; -} +/* eslint-disable */ +export * from '../../../../../plugins/expressions/public/types/style'; +export { + FontLabel as FontFamily, + FontValue, +} from '../../../../../plugins/expressions/public/fonts'; +/* eslint-enable */ diff --git a/src/legacy/core_plugins/interpreter/test_helpers.ts b/src/legacy/core_plugins/interpreter/test_helpers.ts index 1f39a8271367c6..0e34f42b01544f 100644 --- a/src/legacy/core_plugins/interpreter/test_helpers.ts +++ b/src/legacy/core_plugins/interpreter/test_helpers.ts @@ -17,17 +17,5 @@ * under the License. */ -import { mapValues } from 'lodash'; -import { AnyExpressionFunction, FunctionHandlers } from './types'; - -// Takes a function spec and passes in default args, -// overriding with any provided args. -export const functionWrapper = (fnSpec: () => T) => { - const spec = fnSpec(); - const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); - return ( - context: object | null, - args: Record = {}, - handlers: FunctionHandlers = {} - ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); -}; +// eslint-disable-next-line +export * from '../../../plugins/expressions/public/functions/tests/utils'; diff --git a/src/legacy/core_plugins/interpreter/types/arguments.ts b/src/legacy/core_plugins/interpreter/types/arguments.ts index 3e17166a7c434a..35566381d010d8 100644 --- a/src/legacy/core_plugins/interpreter/types/arguments.ts +++ b/src/legacy/core_plugins/interpreter/types/arguments.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types/arguments'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types/arguments'; diff --git a/src/legacy/core_plugins/interpreter/types/common.ts b/src/legacy/core_plugins/interpreter/types/common.ts index dce984a0bd5568..99a7d2dc92f062 100644 --- a/src/legacy/core_plugins/interpreter/types/common.ts +++ b/src/legacy/core_plugins/interpreter/types/common.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types/common'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types/common'; diff --git a/src/legacy/core_plugins/interpreter/types/functions.ts b/src/legacy/core_plugins/interpreter/types/functions.ts index 789bb29990cb44..9a99a78281a0c1 100644 --- a/src/legacy/core_plugins/interpreter/types/functions.ts +++ b/src/legacy/core_plugins/interpreter/types/functions.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types/functions'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types/functions'; diff --git a/src/legacy/core_plugins/interpreter/types/index.ts b/src/legacy/core_plugins/interpreter/types/index.ts index c178bee26a7cdf..6cefc47a678d4a 100644 --- a/src/legacy/core_plugins/interpreter/types/index.ts +++ b/src/legacy/core_plugins/interpreter/types/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from '../../../../plugins/expressions/common'; +export * from '../../../../plugins/expressions/public'; diff --git a/src/legacy/core_plugins/interpreter/types/types.ts b/src/legacy/core_plugins/interpreter/types/types.ts index d3f1cb3987b000..aacb63eaace0c8 100644 --- a/src/legacy/core_plugins/interpreter/types/types.ts +++ b/src/legacy/core_plugins/interpreter/types/types.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types'; diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx index f7e16ead932fa3..e341b7c4a5a866 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx @@ -65,7 +65,7 @@ const indexPattern = { }, metaFields: ['_index', '_score'], flattenHit: undefined, - formatHit: jest.fn(hit => hit), + formatHit: jest.fn(hit => hit._source), } as IndexPattern; indexPattern.flattenHit = flattenHitWrapper(indexPattern, indexPattern.metaFields); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx index 8309eaa403f4c9..7158739e5d4375 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx @@ -19,7 +19,7 @@ import React, { useState } from 'react'; import { DocViewRenderProps } from 'ui/registry/doc_views'; import { DocViewTableRow } from './table_row'; -import { formatValue, arrayContainsObjects } from './table_helper'; +import { arrayContainsObjects, trimAngularSpan } from './table_helper'; const COLLAPSE_LINE_LENGTH = 350; @@ -48,8 +48,9 @@ export function DocViewTable({ .sort() .map(field => { const valueRaw = flattened[field]; - const value = formatValue(valueRaw, formatted[field]); - const isCollapsible = typeof value === 'string' && value.length > COLLAPSE_LINE_LENGTH; + const value = trimAngularSpan(String(formatted[field])); + + const isCollapsible = value.length > COLLAPSE_LINE_LENGTH; const isCollapsed = isCollapsible && !fieldRowOpen[field]; const toggleColumn = onRemoveColumn && onAddColumn && Array.isArray(columns) diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts index f075e06c7651f5..2402d4dddb874f 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts @@ -16,91 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { - replaceMarkWithReactDom, - convertAngularHtml, - arrayContainsObjects, - formatValue, -} from './table_helper'; - -describe('replaceMarkWithReactDom', () => { - it(`converts test to react nodes`, () => { - const actual = replaceMarkWithReactDom( - 'marked1 blablabla marked2 end' - ); - expect(actual).toMatchInlineSnapshot(` - - - - - marked1 - - blablabla - - - - marked2 - - end - - - `); - }); - - it(`doesn't convert invalid markup to react dom nodes`, () => { - const actual = replaceMarkWithReactDom('test sdf sdf'); - expect(actual).toMatchInlineSnapshot(` - - - test sdf - - - sdf - - - - - `); - }); - - it(`returns strings without markup unchanged `, () => { - const actual = replaceMarkWithReactDom('blablabla'); - expect(actual).toMatchInlineSnapshot(` - - blablabla - - `); - }); -}); - -describe('convertAngularHtml', () => { - it(`converts html for usage in angular to usage in react`, () => { - const actual = convertAngularHtml('Good morning!'); - expect(actual).toMatchInlineSnapshot(`"Good morning!"`); - }); - it(`converts html containing for usage in react`, () => { - const actual = convertAngularHtml( - 'Good morningdear reviewer!' - ); - expect(actual).toMatchInlineSnapshot(` - - Good - - - morning - - dear - - - - reviewer - - ! - - - `); - }); -}); +import { arrayContainsObjects } from './table_helper'; describe('arrayContainsObjects', () => { it(`returns false for an array of primitives`, () => { @@ -128,50 +44,3 @@ describe('arrayContainsObjects', () => { expect(actual).toBeFalsy(); }); }); - -describe('formatValue', () => { - it(`formats an array of objects`, () => { - const actual = formatValue([{ test: '123' }, ''], ''); - expect(actual).toMatchInlineSnapshot(` - "{ - \\"test\\": \\"123\\" - } - \\"\\"" - `); - }); - it(`formats an array of primitives`, () => { - const actual = formatValue(['test1', 'test2'], ''); - expect(actual).toMatchInlineSnapshot(`"test1, test2"`); - }); - it(`formats an object`, () => { - const actual = formatValue({ test: 1 }, ''); - expect(actual).toMatchInlineSnapshot(` - "{ - \\"test\\": 1 - }" - `); - }); - it(`formats an angular formatted string `, () => { - const actual = formatValue( - '', - 'Good morningdear reviewer!' - ); - expect(actual).toMatchInlineSnapshot(` - - Good - - - morning - - dear - - - - reviewer - - ! - - - `); - }); -}); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx index e959ec336bf3a1..8835e95022d7c6 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx @@ -16,70 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; -import { unescape } from 'lodash'; -/** - * Convert markup of the given string to ReactNodes - * @param text - */ -export function replaceMarkWithReactDom(text: string): React.ReactNode { - return ( - <> - {text.split('').map((markedText, idx) => { - const sub = markedText.split(''); - if (sub.length === 1) { - return markedText; - } - return ( - - {sub[0]} - {sub[1]} - - ); - })} - - ); -} - -/** - * Current html of the formatter is angular flavored, this current workaround - * should be removed when all consumers of the formatHit function are react based - */ -export function convertAngularHtml(html: string): string | React.ReactNode { - if (typeof html === 'string') { - const cleaned = html.replace('', '').replace('', ''); - const unescaped = unescape(cleaned); - if (unescaped.indexOf('') !== -1) { - return replaceMarkWithReactDom(unescaped); - } - return unescaped; - } - return html; -} /** * Returns true if the given array contains at least 1 object */ -export function arrayContainsObjects(value: unknown[]) { +export function arrayContainsObjects(value: unknown[]): boolean { return Array.isArray(value) && value.some(v => typeof v === 'object' && v !== null); } /** - * The current field formatter provides html for angular usage - * This html is cleaned up and prepared for usage in the react world - * Furthermore test are converted to ReactNodes + * Removes markup added by kibana fields html formatter */ -export function formatValue( - value: null | string | number | boolean | object | Array, - valueFormatted: string -): string | React.ReactNode { - if (Array.isArray(value) && arrayContainsObjects(value)) { - return value.map(v => JSON.stringify(v, null, 2)).join('\n'); - } else if (Array.isArray(value)) { - return value.join(', '); - } else if (typeof value === 'object' && value !== null) { - return JSON.stringify(value, null, 2); - } else { - return typeof valueFormatted === 'string' ? convertAngularHtml(valueFormatted) : String(value); - } +export function trimAngularSpan(text: string): string { + return text.replace(/^/, '').replace(/<\/span>$/, ''); } diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx index 2059e35b2c42ea..045e8093124a6c 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx @@ -85,7 +85,7 @@ export function DocViewTableRow({ )} - + {isCollapsible && ( @@ -93,9 +93,16 @@ export function DocViewTableRow({ )} {displayUnderscoreWarning && } {displayNoMappingWarning && } -
- {value} -
+
); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx index 282c2e6a356bff..b614e00ba8cd08 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx @@ -34,6 +34,12 @@ interface NumberInputOptionProps { setValue: (paramName: ParamName, value: number | '') => void; } +/** + * Do not use this component anymore. + * Please, use NumberInputOption in 'required_number_input.tsx'. + * It is required for compatibility with TS 3.7.0 + * This should be removed in the future + */ function NumberInputOption({ disabled, error, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx new file mode 100644 index 00000000000000..7b62016c4e502c --- /dev/null +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx @@ -0,0 +1,87 @@ +/* + * 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, { ReactNode, useCallback, ChangeEvent } from 'react'; +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; +import { useValidation } from './utils'; + +interface NumberInputOptionProps { + disabled?: boolean; + error?: ReactNode; + isInvalid?: boolean; + label?: React.ReactNode; + max?: number; + min?: number; + paramName: ParamName; + step?: number; + value: number | null; + 'data-test-subj'?: string; + setValue(paramName: ParamName, value: number | null): void; + setValidity(paramName: ParamName, isValid: boolean): void; +} + +/** + * Use only this component instead of NumberInputOption in 'number_input.tsx'. + * It is required for compatibility with TS 3.7.0 + * + * @param {number} props.value Should be numeric only + */ +function NumberInputOption({ + disabled, + error, + isInvalid, + label, + max, + min, + paramName, + step, + value, + setValue, + setValidity, + 'data-test-subj': dataTestSubj, +}: NumberInputOptionProps) { + const isValid = value !== null; + useValidation(setValidity, paramName, isValid); + + const onChange = useCallback( + (ev: ChangeEvent) => + setValue(paramName, isNaN(ev.target.valueAsNumber) ? null : ev.target.valueAsNumber), + [setValue, paramName] + ); + + return ( + + + + ); +} + +export { NumberInputOption }; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts new file mode 100644 index 00000000000000..d51631106dda76 --- /dev/null +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts @@ -0,0 +1,34 @@ +/* + * 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 { useEffect } from 'react'; + +function useValidation( + setValidity: (paramName: ParamName, isValid: boolean) => void, + paramName: ParamName, + isValid: boolean +) { + useEffect(() => { + setValidity(paramName, isValid); + + return () => setValidity(paramName, true); + }, [isValid, paramName, setValidity]); +} + +export { useValidation }; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx index a3a41d2249b329..1dd1ab49d9a47d 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx @@ -29,7 +29,7 @@ interface ValidationWrapperProps extends VisOptionsProps { } interface Item { - valid: boolean; + isValid: boolean; } function ValidationWrapper({ @@ -37,20 +37,17 @@ function ValidationWrapper({ ...rest }: ValidationWrapperProps) { const [panelState, setPanelState] = useState({} as { [key: string]: Item }); - const isPanelValid = Object.values(panelState).every(item => item.valid); + const isPanelValid = Object.values(panelState).every(item => item.isValid); const { setValidity } = rest; - const setValidityHandler = useCallback( - (paramName: string, isValid: boolean) => { - setPanelState({ - ...panelState, - [paramName]: { - valid: isValid, - }, - }); - }, - [panelState] - ); + const setValidityHandler = useCallback((paramName: string, isValid: boolean) => { + setPanelState(state => ({ + ...state, + [paramName]: { + isValid, + }, + })); + }, []); useEffect(() => { setValidity(isPanelValid); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx index c1fa3475470f13..1045543512c6b6 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx @@ -17,14 +17,16 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common'; -import { SetColorSchemaOptionsValue } from '../../common/color_schema'; import { GaugeOptionsInternalProps } from '.'; +import { ColorSchemaVislibParams } from '../../../types'; +import { Gauge } from '../../../gauge'; function RangesPanel({ setGaugeValue, @@ -35,6 +37,22 @@ function RangesPanel({ uiState, vis, }: GaugeOptionsInternalProps) { + const setColorSchemaOptions = useCallback( + (paramName: T, value: ColorSchemaVislibParams[T]) => { + setGaugeValue(paramName, value as Gauge[T]); + // set outline if color schema is changed to greys + // if outline wasn't set explicitly yet + if ( + paramName === 'colorSchema' && + (value as string) === ColorSchemas.Greys && + typeof stateParams.gauge.outline === 'undefined' + ) { + setGaugeValue('outline', true); + } + }, + [setGaugeValue, stateParams] + ); + return ( @@ -84,7 +102,16 @@ function RangesPanel({ colorSchemas={vis.type.editorConfig.collections.colorSchemas} invertColors={stateParams.gauge.invertColors} uiState={uiState} - setValue={setGaugeValue as SetColorSchemaOptionsValue} + setValue={setColorSchemaOptions} + /> + + { describe('boundsMargin', () => { it('should set validity as true when value is positive', () => { - const comp = shallow(); - comp.find({ paramName: BOUNDS_MARGIN }).prop('setValue')(BOUNDS_MARGIN, 5); + defaultProps.axis.scale.boundsMargin = 5; + mount(); expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, true); }); it('should set validity as true when value is empty', () => { - const comp = shallow(); - comp.find({ paramName: BOUNDS_MARGIN }).prop('setValue')(BOUNDS_MARGIN, ''); + const comp = mount(); + comp.setProps({ + axis: { ...valueAxis, scale: { ...valueAxis.scale, boundsMargin: undefined } }, + }); expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, true); }); it('should set validity as false when value is negative', () => { defaultProps.axis.scale.defaultYExtents = true; - const comp = shallow(); - comp.find({ paramName: BOUNDS_MARGIN }).prop('setValue')(BOUNDS_MARGIN, -1); + const comp = mount(); + comp.setProps({ + axis: { ...valueAxis, scale: { ...valueAxis.scale, boundsMargin: -1 } }, + }); expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, false); }); @@ -103,7 +107,6 @@ describe('CustomExtentsOptions component', () => { const comp = shallow(); comp.find({ paramName: DEFAULT_Y_EXTENTS }).prop('setValue')(DEFAULT_Y_EXTENTS, false); - expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, true); const newScale = { ...defaultProps.axis.scale, boundsMargin: undefined, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx index e04d8e646160e0..df7eedd2c0ea18 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { ValueAxis } from '../../../types'; @@ -38,21 +38,18 @@ function CustomExtentsOptions({ setValueAxis, setValueAxisScale, }: CustomExtentsOptionsProps) { - const [isBoundsMarginValid, setIsBoundsMarginValid] = useState(true); const invalidBoundsMarginMessage = i18n.translate( 'kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin', { defaultMessage: 'Bounds margin must be greater than or equal to 0.' } ); - const setBoundsMargin = useCallback( - (paramName: 'boundsMargin', value: number | '') => { - const isValid = value === '' ? true : value >= 0; - setIsBoundsMarginValid(isValid); - setMultipleValidity('boundsMargin', isValid); + const isBoundsMarginValid = + !axis.scale.defaultYExtents || !axis.scale.boundsMargin || axis.scale.boundsMargin >= 0; - setValueAxisScale(paramName, value); - }, - [setMultipleValidity, setValueAxisScale] + const setBoundsMargin = useCallback( + (paramName: 'boundsMargin', value: number | '') => + setValueAxisScale(paramName, value === '' ? undefined : value), + [setValueAxisScale] ); const onDefaultYExtentsChange = useCallback( @@ -60,7 +57,6 @@ function CustomExtentsOptions({ const scale = { ...axis.scale, [paramName]: value }; if (!scale.defaultYExtents) { delete scale.boundsMargin; - setMultipleValidity('boundsMargin', true); } setValueAxis('scale', scale); }, @@ -79,6 +75,12 @@ function CustomExtentsOptions({ [setValueAxis, axis.scale] ); + useEffect(() => { + setMultipleValidity('boundsMargin', isBoundsMarginValid); + + return () => setMultipleValidity('boundsMargin', true); + }, [isBoundsMarginValid, setMultipleValidity]); + return ( <> setMultipleValidity('yExtents', true); - }, [isValid]); + }, [isValid, setMultipleValidity]); return ( diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx index 8e3f66d12b9bdf..ed3b52b83c2345 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx @@ -21,13 +21,12 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { BasicOptions, SwitchOption } from '../../common'; +import { BasicOptions, SwitchOption, ValidationVisOptionsProps } from '../../common'; import { GridPanel } from './grid_panel'; import { ThresholdPanel } from './threshold_panel'; import { BasicVislibParams } from '../../../types'; -function PointSeriesOptions(props: VisOptionsProps) { +function PointSeriesOptions(props: ValidationVisOptionsProps) { const { stateParams, setValue, vis } = props; return ( diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx index 49e56e377a8d56..591ad2eb3a001a 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx @@ -21,11 +21,16 @@ import { EuiPanel, EuiTitle, EuiColorPicker, EuiFormRow, EuiSpacer } from '@elas import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { NumberInputOption, SelectOption, SwitchOption } from '../../common'; +import { SelectOption, SwitchOption, ValidationVisOptionsProps } from '../../common'; +import { NumberInputOption } from '../../common/required_number_input'; import { BasicVislibParams } from '../../../types'; -function ThresholdPanel({ stateParams, setValue, vis }: VisOptionsProps) { +function ThresholdPanel({ + stateParams, + setValue, + setMultipleValidity, + vis, +}: ValidationVisOptionsProps) { const setThresholdLine = useCallback( ( paramName: T, @@ -39,6 +44,12 @@ function ThresholdPanel({ stateParams, setValue, vis }: VisOptionsProps + setMultipleValidity(`thresholdLine__${paramName}`, isValid), + [setMultipleValidity] + ); + return ( @@ -72,6 +83,7 @@ function ThresholdPanel({ stateParams, setValue, vis }: VisOptionsProps ) => ( + + ), }, ]; } diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index 03b2f5b898345c..e024e98acb343b 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -285,6 +285,50 @@ function transformFilterStringToQueryObject(doc) { } return newDoc; } +function transformSplitFiltersStringToQueryObject(doc) { + // Migrate split_filters in TSVB objects that weren't migrated in 7.3 + // If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly + const newDoc = cloneDeep(doc); + const visStateJSON = get(doc, 'attributes.visState'); + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // let it go, the data is invalid and we'll leave it as is + } + if (visState) { + const visType = get(visState, 'params.type'); + const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; + if (tsvbTypes.indexOf(visType) === -1) { + // skip + return doc; + } + // migrate the series split_filter filters + const series = get(visState, 'params.series') || []; + series.forEach(item => { + // series item split filters filter + if (item.split_filters) { + const splitFilters = get(item, 'split_filters') || []; + if (splitFilters.length > 0) { + // only transform split_filter filters if we have filters + splitFilters.forEach(filter => { + if (typeof filter.filter === 'string') { + const filterfilterObject = { + query: filter.filter, + language: 'lucene', + }; + filter.filter = filterfilterObject; + } + }); + } + } + }); + newDoc.attributes.visState = JSON.stringify(visState); + } + } + return newDoc; +} function migrateFiltersAggQuery(doc) { const visStateJSON = get(doc, 'attributes.visState'); @@ -416,6 +460,31 @@ function migrateFiltersAggQueryStringQueries(doc) { } +function migrateSubTypeAndParentFieldProperties(doc) { + if (!doc.attributes.fields) return doc; + + const fieldsString = doc.attributes.fields; + const fields = JSON.parse(fieldsString); + const migratedFields = fields.map(field => { + if (field.subType === 'multi') { + return { + ...omit(field, 'parent'), + subType: { multi: { parent: field.parent } } + }; + } + + return field; + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + fields: JSON.stringify(migratedFields), + } + }; +} + const executeMigrations720 = flow( migratePercentileRankAggregation, migrateDateHistogramAggregation @@ -435,6 +504,10 @@ const executeSearchMigrations740 = flow( migrateSearchSortToNestedArray, ); +const executeMigrations742 = flow( + transformSplitFiltersStringToQueryObject +); + export const migrations = { 'index-pattern': { '6.5.0': doc => { @@ -442,6 +515,9 @@ export const migrations = { doc.attributes.typeMeta = doc.attributes.typeMeta || undefined; return doc; }, + '7.6.0': flow( + migrateSubTypeAndParentFieldProperties + ) }, visualization: { /** @@ -541,6 +617,8 @@ export const migrations = { '7.2.0': doc => executeMigrations720(doc), '7.3.0': executeMigrations730, '7.3.1': executeVisualizationMigrations731, + // migrate split_filters that were not migrated in 7.3.0 (transformFilterStringToQueryObject). + '7.4.2': executeMigrations742, }, dashboard: { '7.0.0': doc => { diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index 77a4c7eabe86e3..14f1e8c80e349c 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -56,6 +56,29 @@ Object { `); }); }); + + describe('7.6.0', function () { + const migrate = doc => migrations['index-pattern']['7.6.0'](doc); + + it('should remove the parent property and update the subType prop on every field that has them', () => { + const input = { + attributes: { + title: 'test', + // eslint-disable-next-line max-len + fields: '[{"name":"customer_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":"multi","parent":"customer_name"}]', + }, + }; + const expected = { + attributes: { + title: 'test', + // eslint-disable-next-line max-len + fields: '[{"name":"customer_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_name"}}}]', + }, + }; + + expect(migrate(input)).toEqual(expected); + }); + }); }); describe('visualization', () => { @@ -1180,6 +1203,123 @@ Array [ expect(migratedDoc).toEqual({ attributes: { visState: JSON.stringify(expected) } }); }); }); + describe('7.4.2 tsvb split_filters migration', () => { + const migrate = doc => migrations.visualization['7.4.2'](doc); + const generateDoc = ({ params }) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + it('should change series item filters from a string into an object for all filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ] + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(Object.keys(timeSeriesParams.filter)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual( + { query: 'bytes:>1000', language: 'lucene' } + ); + }); + it('should change series item split filters when there is no filter item', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene' + } + } + ], + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual( + { query: 'bytes:>1000', language: 'lucene' } + ); + }); + it('should not convert split_filters to objects if there are no split filter filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [], + }, + ] + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters).not.toHaveProperty('query'); + }); + it('should do nothing if a split_filter is already a query:language object', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ + filter: { + query: 'bytes:>1000', + language: 'lucene', + } + }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene' + } + } + ], + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters[0].filter.query).toEqual('bytes:>1000'); + expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); + + }); + }); }); describe('dashboard', () => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 7a0398e86a60da..5fa3a938ed9dfc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -35,7 +35,6 @@ import { } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; -import { Filter } from '@kbn/es-query'; import { TimeRange } from 'src/plugins/data/public'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; @@ -46,6 +45,7 @@ import { Subscription } from 'rxjs'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; +import { esFilters } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; @@ -55,7 +55,7 @@ export interface DashboardAppScope extends ng.IScope { screenTitle: string; model: { query: Query; - filters: Filter[]; + filters: esFilters.Filter[]; timeRestore: boolean; title: string; description: string; @@ -81,9 +81,9 @@ export interface DashboardAppScope extends ng.IScope { isPaused: boolean; refreshInterval: any; }) => void; - onFiltersUpdated: (filters: Filter[]) => void; + onFiltersUpdated: (filters: esFilters.Filter[]) => void; onCancelApplyFilters: () => void; - onApplyFilters: (filters: Filter[]) => void; + onApplyFilters: (filters: esFilters.Filter[]) => void; onQuerySaved: (savedQuery: SavedQuery) => void; onSavedQueryUpdated: (savedQuery: SavedQuery) => void; onClearSavedQuery: () => void; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index abf7b22a6e48c6..adf0e1e084a644 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -48,7 +48,6 @@ import { } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; -import { Filter } from '@kbn/es-query'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { Query, SavedQuery } from 'src/legacy/core_plugins/data/public'; @@ -57,8 +56,9 @@ import { capabilities } from 'ui/capabilities'; import { Subscription } from 'rxjs'; import { npStart } from 'ui/new_platform'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import { extractTimeFilter, changeTimeFilter } from '../../../data/public'; +import { extractTimeFilter, changeTimeFilter } from '../../../../../plugins/data/public'; import { start as data } from '../../../data/public/legacy'; +import { esFilters } from '../../../../../plugins/data/public'; import { DashboardContainer, @@ -514,7 +514,7 @@ export class DashboardAppController { } ); - $scope.$watch('appState.$newFilters', (filters: Filter[] = []) => { + $scope.$watch('appState.$newFilters', (filters: esFilters.Filter[] = []) => { if (filters.length === 1) { $scope.onApplyFilters(filters); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index a25ce1e607f9a4..5e81373001bf57 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -23,7 +23,7 @@ import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; import { AppStateClass } from 'ui/state_management/app_state'; import { DashboardAppState } from './types'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange, TimefilterContract } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; import { InputTimeRange } from 'ui/timefilter'; @@ -33,22 +33,19 @@ jest.mock('ui/registry/field_formats', () => ({ }, })); -import { dataPluginMock } from '../../../../core_plugins/data/public/mocks'; -const dataSetupMock = dataPluginMock.createSetup(); - describe('DashboardState', function() { let dashboardState: DashboardStateManager; const savedDashboard = getSavedDashboardMock(); let mockTime: TimeRange = { to: 'now', from: 'now-15m' }; - const mockTimefilter = dataSetupMock.timefilter!.timefilter; - - mockTimefilter.setTime.mockImplementation((time: InputTimeRange) => { - mockTime = time as TimeRange; - }); - mockTimefilter.getTime.mockImplementation(() => { - return mockTime; - }); + const mockTimefilter = { + getTime: () => { + return mockTime; + }, + setTime: (time: InputTimeRange) => { + mockTime = time as TimeRange; + }, + } as TimefilterContract; function initDashboardState() { dashboardState = new DashboardStateManager({ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 7c1fc771de3491..8ffabe5add1c34 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { Filter } from '@kbn/es-query'; import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; import { Timefilter } from 'ui/timefilter'; import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; @@ -29,6 +28,7 @@ import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; +import { esFilters } from '../../../../../../src/plugins/data/public'; import { Query } from '../../../data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; @@ -50,7 +50,7 @@ export class DashboardStateManager { public lastSavedDashboardFilters: { timeTo?: string | Moment; timeFrom?: string | Moment; - filterBars: Filter[]; + filterBars: esFilters.Filter[]; query: Query; }; private stateDefaults: DashboardAppStateDefaults; @@ -303,7 +303,7 @@ export class DashboardStateManager { return this.savedDashboard.timeRestore; } - public getLastSavedFilterBars(): Filter[] { + public getLastSavedFilterBars(): esFilters.Filter[] { return this.lastSavedDashboardFilters.filterBars; } @@ -461,7 +461,7 @@ export class DashboardStateManager { * Applies the current filter state to the dashboard. * @param filter An array of filter bar filters. */ - public applyFilters(query: Query, filters: Filter[]) { + public applyFilters(query: Query, filters: esFilters.Filter[]) { this.appState.query = query; this.savedDashboard.searchSource.setField('query', query); this.savedDashboard.searchSource.setField('filter', filters); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts index 1fd50081c58bd6..19a0c32210737b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import moment, { Moment } from 'moment'; -import { Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; /** * @typedef {Object} QueryFilter @@ -65,9 +65,9 @@ export class FilterUtils { * @param filters {Array.} * @returns {Array.} */ - public static cleanFiltersForComparison(filters: Filter[]) { + public static cleanFiltersForComparison(filters: esFilters.Filter[]) { return _.map(filters, filter => { - const f: Partial = _.omit(filter, ['$$hashKey', '$state']); + const f: Partial = _.omit(filter, ['$$hashKey', '$state']); if (f.meta) { // f.meta.value is the value displayed in the filter bar. // It may also be loaded differently and shouldn't be used in this comparison. diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts index 1f503ee6754070..ae3edae3b85d61 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts @@ -18,12 +18,12 @@ */ import { moveFiltersToQuery, Pre600FilterQuery } from './move_filters_to_query'; -import { Filter, FilterStateStore } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; -const filter: Filter = { +const filter: esFilters.Filter = { meta: { disabled: false, negate: false, alias: '' }, query: {}, - $state: { store: FilterStateStore.APP_STATE }, + $state: { store: esFilters.FilterStateStore.APP_STATE }, }; const queryFilter: Pre600FilterQuery = { @@ -38,7 +38,7 @@ test('Migrates an old filter query into the query field', () => { expect(newSearchSource).toEqual({ filter: [ { - $state: { store: FilterStateStore.APP_STATE }, + $state: { store: esFilters.FilterStateStore.APP_STATE }, meta: { alias: '', disabled: false, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts index 153bdeba9d35f1..8522495b9dedb2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts @@ -18,7 +18,7 @@ */ import { Query } from 'src/legacy/core_plugins/data/public'; -import { Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; export interface Pre600FilterQuery { // pre 6.0.0 global query:queryString:options were stored per dashboard and would @@ -30,18 +30,18 @@ export interface Pre600FilterQuery { export interface SearchSourcePre600 { // I encountered at least one export from 7.0.0-alpha that was missing the filter property in here. // The maps data in esarchives actually has it, but I don't know how/when they created it. - filter?: Array; + filter?: Array; } export interface SearchSource730 { - filter: Filter[]; + filter: esFilters.Filter[]; query: Query; highlightAll?: boolean; version?: boolean; } -function isQueryFilter(filter: Filter | { query: unknown }): filter is Pre600FilterQuery { - return filter.query && !(filter as Filter).meta; +function isQueryFilter(filter: esFilters.Filter | { query: unknown }): filter is Pre600FilterQuery { + return filter.query && !(filter as esFilters.Filter).meta; } export function moveFiltersToQuery( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts index 1231ca28ed014b..5b860b0a2cc7c1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts @@ -19,10 +19,9 @@ import { SearchSource } from 'ui/courier'; import { SavedObject } from 'ui/saved_objects/saved_object'; -import moment from 'moment'; import { RefreshInterval } from 'src/plugins/data/public'; import { Query } from 'src/legacy/core_plugins/data/public'; -import { Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; export interface SavedObjectDashboard extends SavedObject { id?: string; @@ -41,5 +40,5 @@ export interface SavedObjectDashboard extends SavedObject { destroy: () => void; refreshInterval?: RefreshInterval; getQuery(): Query; - getFilters(): Filter[]; + getFilters(): esFilters.Filter[]; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/types.ts index ccccc89004e365..5aaca7b62094f8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/types.ts @@ -18,7 +18,6 @@ */ import { AppState } from 'ui/state_management/app_state'; -import { Filter } from '@kbn/es-query'; import { Query } from 'src/legacy/core_plugins/data/public'; import { AppState as TAppState } from 'ui/state_management/app_state'; import { ViewMode } from 'src/plugins/embeddable/public'; @@ -30,6 +29,7 @@ import { RawSavedDashboardPanel640To720, RawSavedDashboardPanel730ToLatest, } from './migrations/types'; +import { esFilters } from '../../../../../plugins/data/public'; export type NavAction = (anchorElement?: any) => void; @@ -110,7 +110,7 @@ export interface DashboardAppStateParameters { useMargins: boolean; }; query: Query | string; - filters: Filter[]; + filters: esFilters.Filter[]; viewMode: ViewMode; savedQuery?: string; } diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js index 5e8cfc8e1609cf..9ac76bfcfe04e2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js @@ -23,7 +23,6 @@ import _ from 'lodash'; import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/private'; import '../../components/field_chooser/discover_field'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js index 3130ac29eb84db..3ddee3495f36d8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js @@ -22,7 +22,6 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import { fieldCalculator } from '../../components/field_chooser/lib/field_calculator'; import expect from '@kbn/expect'; -import 'ui/private'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; // Load the kibana app dependencies. diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js index c2be750ec7f631..a5b55e50eb90e9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js @@ -23,7 +23,6 @@ import _ from 'lodash'; import sinon from 'sinon'; import expect from '@kbn/expect'; import $ from 'jquery'; -import 'ui/private'; import '../../components/field_chooser/field_chooser'; import FixturesHitsProvider from 'fixtures/hits'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss index 0b0bd12cb268b8..b311dd8a347789 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_index.scss @@ -11,7 +11,7 @@ @import 'components/fetch_error/index'; @import 'components/field_chooser/index'; @import 'angular/directives/index'; -@import 'doc_table/index'; +@import 'angular/doc_table/index'; @import 'hacks'; @@ -23,4 +23,4 @@ @import 'doc_viewer/index'; // Context styles -@import 'context/index'; +@import 'angular/context/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/context/index.html b/src/legacy/core_plugins/kibana/public/discover/angular/context.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/index.html rename to src/legacy/core_plugins/kibana/public/discover/angular/context.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context.js b/src/legacy/core_plugins/kibana/public/discover/angular/context.js new file mode 100644 index 00000000000000..58d1626ca4b14d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context.js @@ -0,0 +1,103 @@ +/* + * 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 _ from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { getServices, subscribeWithScope } from './../kibana_services'; + +import './context_app'; +import contextAppRouteTemplate from './context.html'; +import { getRootBreadcrumbs } from '../breadcrumbs'; +const { FilterBarQueryFilterProvider, uiRoutes, chrome } = getServices(); + +const k7Breadcrumbs = $route => { + const { indexPattern } = $route.current.locals; + const { id } = $route.current.params; + + return [ + ...getRootBreadcrumbs(), + { + text: i18n.translate('kbn.context.breadcrumb', { + defaultMessage: 'Context of {indexPatternTitle}#{docId}', + values: { + indexPatternTitle: indexPattern.title, + docId: id, + }, + }), + }, + ]; +}; + +uiRoutes + // deprecated route, kept for compatibility + // should be removed in the future + .when('/context/:indexPatternId/:type/:id*', { + redirectTo: '/context/:indexPatternId/:id', + }) + .when('/context/:indexPatternId/:id*', { + controller: ContextAppRouteController, + k7Breadcrumbs, + controllerAs: 'contextAppRoute', + resolve: { + indexPattern: function ($route, indexPatterns) { + return indexPatterns.get($route.current.params.indexPatternId); + }, + }, + template: contextAppRouteTemplate, + }); + +function ContextAppRouteController($routeParams, $scope, AppState, config, indexPattern, Private) { + const queryFilter = Private(FilterBarQueryFilterProvider); + + this.state = new AppState(createDefaultAppState(config, indexPattern)); + this.state.save(true); + + $scope.$watchGroup( + [ + 'contextAppRoute.state.columns', + 'contextAppRoute.state.predecessorCount', + 'contextAppRoute.state.successorCount', + ], + () => this.state.save(true) + ); + + const updateSubsciption = subscribeWithScope($scope, queryFilter.getUpdates$(), { + next: () => { + this.filters = _.cloneDeep(queryFilter.getFilters()); + }, + }); + + $scope.$on('$destroy', function () { + updateSubsciption.unsubscribe(); + }); + this.anchorId = $routeParams.id; + this.indexPattern = indexPattern; + this.discoverUrl = chrome.navLinks.get('kibana:discover').url; + this.filters = _.cloneDeep(queryFilter.getFilters()); +} + +function createDefaultAppState(config, indexPattern) { + return { + columns: ['_source'], + filters: [], + predecessorCount: parseInt(config.get('context:defaultSize'), 10), + sort: [indexPattern.timeFieldName, 'desc'], + successorCount: parseInt(config.get('context:defaultSize'), 10), + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/context/NOTES.md b/src/legacy/core_plugins/kibana/public/discover/angular/context/NOTES.md similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/NOTES.md rename to src/legacy/core_plugins/kibana/public/discover/angular/context/NOTES.md diff --git a/src/legacy/core_plugins/kibana/public/discover/context/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/context/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/context/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js index ecb22b20e4d86e..f472ff9250eb5b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import moment from 'moment'; -import { SearchSource } from 'ui/courier'; +import { SearchSource } from '../../../../kibana_services'; export function createIndexPatternsStub() { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/predecessors.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/successors.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js index 02a309eaa01659..62bbc6166662f8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; - import { i18n } from '@kbn/i18n'; +import { getServices } from '../../../kibana_services'; -import { SearchSource } from 'ui/courier'; - +const { SearchSource } = getServices(); export function fetchAnchorProvider(indexPatterns) { return async function fetchAnchor( indexPatternId, diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/context/api/context.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts index 48ac59f1f08552..3314bbbf189c4b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts @@ -17,16 +17,14 @@ * under the License. */ -// @ts-ignore -import { SearchSource } from 'ui/courier'; -import { Filter } from '@kbn/es-query'; -import { IndexPatterns, IndexPattern } from 'ui/index_patterns'; +import { IndexPatterns, IndexPattern, getServices } from '../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { @@ -36,6 +34,8 @@ export interface EsHitRecord { } export type EsHitRecordList = EsHitRecord[]; +const { SearchSource } = getServices(); + const DAY_MILLIS = 24 * 60 * 60 * 1000; // look from 1 day up to 10000 days into the past and future @@ -67,7 +67,7 @@ function fetchContextProvider(indexPatterns: IndexPatterns) { tieBreakerField: string, sortDir: SortDirection, size: number, - filters: Filter[] + filters: esFilters.Filter[] ) { if (typeof anchor !== 'object' || anchor === null) { return []; @@ -112,7 +112,7 @@ function fetchContextProvider(indexPatterns: IndexPatterns) { return documents; } - async function createSearchSource(indexPattern: IndexPattern, filters: Filter[]) { + async function createSearchSource(indexPattern: IndexPattern, filters: esFilters.Filter[]) { return new SearchSource() .setParent(false) .setField('index', indexPattern) diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/date_conversion.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/date_conversion.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/date_conversion.test.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/date_conversion.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/sorting.test.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/date_conversion.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/date_conversion.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/date_conversion.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/date_conversion.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts index 9a5436b59714d8..2810e5d9d7e663 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SearchSource } from 'ui/courier'; +import { SearchSource } from '../../../../kibana_services'; import { convertTimeValueToIso } from './date_conversion'; import { SortDirection } from './sorting'; import { EsHitRecordList } from '../context'; diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/generate_intervals.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_search_after.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_search_after.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_search_after.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_search_after.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_sort.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_sort.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts index b673270d7a6457..4a0f531845f46e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { IndexPattern } from '../../../../kibana_services'; export enum SortDirection { asc = 'asc', diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_action_bar.scss b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_action_bar.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_action_bar.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_action_bar.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts similarity index 89% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts index 0942539e63785a..579d9d95c6f719 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; +import { getServices } from '../../../../kibana_services'; import { ActionBar } from './action_bar'; +const { uiModules, wrapInI18nContext } = getServices(); + uiModules.get('apps/context').directive('contextActionBar', function(reactDirective: any) { return reactDirective(wrapInI18nContext(ActionBar)); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_warning.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_warning.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_warning.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_warning.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/index.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/index.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js new file mode 100644 index 00000000000000..b88e54379f448e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js @@ -0,0 +1,192 @@ +/* + * 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 _ from 'lodash'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { toastNotifications } from '../../../kibana_services'; + +import { fetchAnchorProvider } from '../api/anchor'; +import { fetchContextProvider } from '../api/context'; +import { QueryParameterActionsProvider } from '../query_parameters'; +import { FAILURE_REASONS, LOADING_STATUS } from './constants'; +import { MarkdownSimple } from '../../../../../../kibana_react/public'; + +export function QueryActionsProvider(Private, Promise) { + const fetchAnchor = Private(fetchAnchorProvider); + const { fetchSurroundingDocs } = Private(fetchContextProvider); + const { + setPredecessorCount, + setQueryParameters, + setSuccessorCount, + } = Private(QueryParameterActionsProvider); + + const setFailedStatus = (state) => (subject, details = {}) => ( + state.loadingStatus[subject] = { + status: LOADING_STATUS.FAILED, + reason: FAILURE_REASONS.UNKNOWN, + ...details, + } + ); + + const setLoadedStatus = (state) => (subject) => ( + state.loadingStatus[subject] = { + status: LOADING_STATUS.LOADED, + } + ); + + const setLoadingStatus = (state) => (subject) => ( + state.loadingStatus[subject] = { + status: LOADING_STATUS.LOADING, + } + ); + + const fetchAnchorRow = (state) => () => { + const { queryParameters: { indexPatternId, anchorId, sort, tieBreakerField } } = state; + + if (!tieBreakerField) { + return Promise.reject(setFailedStatus(state)('anchor', { + reason: FAILURE_REASONS.INVALID_TIEBREAKER + })); + } + + setLoadingStatus(state)('anchor'); + + return Promise.try(() => ( + fetchAnchor(indexPatternId, anchorId, [_.zipObject([sort]), { [tieBreakerField]: sort[1] }]) + )) + .then( + (anchorDocument) => { + setLoadedStatus(state)('anchor'); + state.rows.anchor = anchorDocument; + return anchorDocument; + }, + (error) => { + setFailedStatus(state)('anchor', { error }); + toastNotifications.addDanger({ + title: i18n.translate('kbn.context.unableToLoadAnchorDocumentDescription', { + defaultMessage: 'Unable to load the anchor document' + }), + text: {error.message}, + }); + throw error; + } + ); + }; + + const fetchSurroundingRows = (type, state) => { + const { + queryParameters: { indexPatternId, filters, sort, tieBreakerField }, + rows: { anchor }, + } = state; + const count = type === 'successors' + ? state.queryParameters.successorCount + : state.queryParameters.predecessorCount; + + if (!tieBreakerField) { + return Promise.reject(setFailedStatus(state)(type, { + reason: FAILURE_REASONS.INVALID_TIEBREAKER + })); + } + + setLoadingStatus(state)(type); + const [sortField, sortDir] = sort; + + return Promise.try(() => ( + fetchSurroundingDocs( + type, + indexPatternId, + anchor, + sortField, + tieBreakerField, + sortDir, + count, + filters + ) + )) + .then( + (documents) => { + setLoadedStatus(state)(type); + state.rows[type] = documents; + return documents; + }, + (error) => { + setFailedStatus(state)(type, { error }); + toastNotifications.addDanger({ + title: i18n.translate('kbn.context.unableToLoadDocumentDescription', { + defaultMessage: 'Unable to load documents' + }), + text: {error.message}, + }); + throw error; + }, + ); + }; + + const fetchContextRows = (state) => () => ( + Promise.all([ + fetchSurroundingRows('predecessors', state), + fetchSurroundingRows('successors', state), + ]) + ); + + const fetchAllRows = (state) => () => ( + Promise.try(fetchAnchorRow(state)) + .then(fetchContextRows(state)) + ); + + const fetchContextRowsWithNewQueryParameters = (state) => (queryParameters) => { + setQueryParameters(state)(queryParameters); + return fetchContextRows(state)(); + }; + + const fetchAllRowsWithNewQueryParameters = (state) => (queryParameters) => { + setQueryParameters(state)(queryParameters); + return fetchAllRows(state)(); + }; + + const fetchGivenPredecessorRows = (state) => (count) => { + setPredecessorCount(state)(count); + return fetchSurroundingRows('predecessors', state); + }; + + const fetchGivenSuccessorRows = (state) => (count) => { + setSuccessorCount(state)(count); + return fetchSurroundingRows('successors', state); + }; + + const setAllRows = (state) => (predecessorRows, anchorRow, successorRows) => ( + state.rows.all = [ + ...(predecessorRows || []), + ...(anchorRow ? [anchorRow] : []), + ...(successorRows || []), + ] + ); + + return { + fetchAllRows, + fetchAllRowsWithNewQueryParameters, + fetchAnchorRow, + fetchContextRows, + fetchContextRowsWithNewQueryParameters, + fetchGivenPredecessorRows, + fetchGivenSuccessorRows, + setAllRows, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/constants.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/constants.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query/constants.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/constants.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/state.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/state.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query/state.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/state.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/_utils.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/_utils.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/_utils.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/_utils.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js index 1c96cbeec04a3a..b136b03bd500bf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js @@ -20,9 +20,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import sinon from 'sinon'; - -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; - +import { getServices } from '../../../../kibana_services'; import { createStateStub } from './_utils'; import { QueryParameterActionsProvider } from '../actions'; @@ -36,7 +34,7 @@ describe('context app', function () { beforeEach(ngMock.inject(function createPrivateStubs(Private) { filterManagerStub = createQueryFilterStub(); - Private.stub(FilterBarQueryFilterProvider, filterManagerStub); + Private.stub(getServices().FilterBarQueryFilterProvider, filterManagerStub); addFilter = Private(QueryParameterActionsProvider).addFilter; })); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_predecessor_count.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_query_parameters.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_successor_count.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js new file mode 100644 index 00000000000000..9f7b180e8fe7db --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -0,0 +1,80 @@ +/* + * 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 _ from 'lodash'; +import { getServices, getFilterGenerator } from '../../../kibana_services'; + +import { + MAX_CONTEXT_SIZE, + MIN_CONTEXT_SIZE, + QUERY_PARAMETER_KEYS, +} from './constants'; + + +export function QueryParameterActionsProvider(indexPatterns, Private) { + const queryFilter = Private(getServices().FilterBarQueryFilterProvider); + const filterGen = getFilterGenerator(queryFilter); + + const setPredecessorCount = (state) => (predecessorCount) => ( + state.queryParameters.predecessorCount = clamp( + MIN_CONTEXT_SIZE, + MAX_CONTEXT_SIZE, + predecessorCount, + ) + ); + + const setSuccessorCount = (state) => (successorCount) => ( + state.queryParameters.successorCount = clamp( + MIN_CONTEXT_SIZE, + MAX_CONTEXT_SIZE, + successorCount, + ) + ); + + const setQueryParameters = (state) => (queryParameters) => ( + Object.assign( + state.queryParameters, + _.pick(queryParameters, QUERY_PARAMETER_KEYS), + ) + ); + + const updateFilters = () => filters => { + queryFilter.setFilters(filters); + }; + + const addFilter = (state) => async (field, values, operation) => { + const indexPatternId = state.queryParameters.indexPatternId; + const newFilters = filterGen.generate(field, values, operation, indexPatternId); + queryFilter.addFilters(newFilters); + const indexPattern = await indexPatterns.get(indexPatternId); + indexPattern.popularizeField(field.name, 1); + }; + + return { + addFilter, + updateFilters, + setPredecessorCount, + setQueryParameters, + setSuccessorCount, + }; +} + +function clamp(minimum, maximum, value) { + return Math.max(Math.min(maximum, value), minimum); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/constants.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/constants.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/state.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/state.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/state.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/state.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/app.html b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/app.html rename to src/legacy/core_plugins/kibana/public/discover/angular/context_app.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js new file mode 100644 index 00000000000000..c9856ad7949523 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js @@ -0,0 +1,155 @@ +/* + * 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 _ from 'lodash'; +import { getServices, callAfterBindingsWorkaround } from './../kibana_services'; +import contextAppTemplate from './context_app.html'; +import './context/components/action_bar'; +import { getFirstSortableField } from './context/api/utils/sorting'; +import { + createInitialQueryParametersState, + QueryParameterActionsProvider, + QUERY_PARAMETER_KEYS, +} from './context/query_parameters'; +import { + createInitialLoadingStatusState, + FAILURE_REASONS, + LOADING_STATUS, + QueryActionsProvider, +} from './context/query'; + +const { uiModules, timefilter } = getServices(); + +// load directives +import '../../../../data/public/legacy'; + +const module = uiModules.get('apps/context', [ + 'elasticsearch', + 'kibana', + 'kibana/config', + 'ngRoute', +]); + +module.directive('contextApp', function ContextApp() { + return { + bindToController: true, + controller: callAfterBindingsWorkaround(ContextAppController), + controllerAs: 'contextApp', + restrict: 'E', + scope: { + anchorId: '=', + columns: '=', + indexPattern: '=', + filters: '=', + predecessorCount: '=', + successorCount: '=', + sort: '=', + discoverUrl: '=', + }, + template: contextAppTemplate, + }; +}); + +function ContextAppController($scope, config, Private) { + const queryParameterActions = Private(QueryParameterActionsProvider); + const queryActions = Private(QueryActionsProvider); + + timefilter.disableAutoRefreshSelector(); + timefilter.disableTimeRangeSelector(); + + this.state = createInitialState( + parseInt(config.get('context:step'), 10), + getFirstSortableField(this.indexPattern, config.get('context:tieBreakerFields')), + this.discoverUrl, + ); + + this.actions = _.mapValues({ + ...queryParameterActions, + ...queryActions, + }, (action) => (...args) => action(this.state)(...args)); + + this.constants = { + FAILURE_REASONS, + LOADING_STATUS, + }; + + $scope.$watchGroup([ + () => this.state.rows.predecessors, + () => this.state.rows.anchor, + () => this.state.rows.successors, + ], (newValues) => this.actions.setAllRows(...newValues)); + + /** + * Sync properties to state + */ + $scope.$watchCollection( + () => ({ + ...(_.pick(this, QUERY_PARAMETER_KEYS)), + indexPatternId: this.indexPattern.id, + }), + (newQueryParameters) => { + const { queryParameters } = this.state; + if ( + (newQueryParameters.indexPatternId !== queryParameters.indexPatternId) + || (newQueryParameters.anchorId !== queryParameters.anchorId) + || (!_.isEqual(newQueryParameters.sort, queryParameters.sort)) + ) { + this.actions.fetchAllRowsWithNewQueryParameters(_.cloneDeep(newQueryParameters)); + } else if ( + (newQueryParameters.predecessorCount !== queryParameters.predecessorCount) + || (newQueryParameters.successorCount !== queryParameters.successorCount) + || (!_.isEqual(newQueryParameters.filters, queryParameters.filters)) + ) { + this.actions.fetchContextRowsWithNewQueryParameters(_.cloneDeep(newQueryParameters)); + } + }, + ); + + /** + * Sync state to properties + */ + $scope.$watchCollection( + () => ({ + predecessorCount: this.state.queryParameters.predecessorCount, + successorCount: this.state.queryParameters.successorCount, + }), + (newParameters) => { + _.assign(this, newParameters); + }, + ); +} + + +function createInitialState(defaultStepSize, tieBreakerField, discoverUrl) { + return { + queryParameters: createInitialQueryParametersState(defaultStepSize, tieBreakerField), + rows: { + all: [], + anchor: null, + predecessors: [], + successors: [], + }, + loadingStatus: createInitialLoadingStatusState(), + navigation: { + discover: { + url: discoverUrl, + }, + }, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx index 0dca912653f6c5..ab336396b5bed2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx @@ -23,7 +23,6 @@ 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 { npStart } from 'ui/new_platform'; import { AnnotationDomainTypes, @@ -44,12 +43,9 @@ import { } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; - -import chrome from 'ui/chrome'; -// @ts-ignore: path dynamic for kibana -import { timezoneProvider } from 'ui/vis/lib/timezone'; import { EuiChartThemeType } from '@elastic/eui/src/themes/charts/themes'; import { Subscription } from 'rxjs'; +import { getServices, timezoneProvider } from '../../kibana_services'; export interface DiscoverHistogramProps { chartData: any; @@ -68,12 +64,12 @@ export class DiscoverHistogram extends Component this.setState({ chartsTheme })); } @@ -145,7 +141,7 @@ export class DiscoverHistogram extends Component diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js index 5f6d32681b50e4..b5d3e8a5a01ca2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js @@ -32,6 +32,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; +import { getServices } from '../../kibana_services'; // eslint-disable-next-line react/prefer-stateless-function export class DiscoverNoResults extends Component { @@ -39,7 +40,6 @@ export class DiscoverNoResults extends Component { shardFailures: PropTypes.array, timeFieldName: PropTypes.string, queryLanguage: PropTypes.string, - getDocLink: PropTypes.func.isRequired, }; render() { @@ -47,7 +47,6 @@ export class DiscoverNoResults extends Component { shardFailures, timeFieldName, queryLanguage, - getDocLink, } = this.props; let shardFailuresMessage; @@ -226,7 +225,7 @@ export class DiscoverNoResults extends Component { queryStringSyntaxLink: ( { + return { + getServices: () => ({ + docLinks: { + links: { + query: { + luceneQuerySyntax: 'documentation-link', + }, + }, + }, + }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); describe('DiscoverNoResults', () => { describe('props', () => { describe('shardFailures', () => { test('renders failures list when there are failures', () => { - const shardFailures = [{ - index: 'A', - shard: '1', - reason: { reason: 'Awful error' }, - }, { - index: 'B', - shard: '2', - reason: { reason: 'Bad error' }, - }]; + const shardFailures = [ + { + index: 'A', + shard: '1', + reason: { reason: 'Awful error' }, + }, + { + index: 'B', + shard: '2', + reason: { reason: 'Bad error' }, + }, + ]; - const component = renderWithIntl( - ''} - /> - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); @@ -51,12 +65,7 @@ describe('DiscoverNoResults', () => { test(`doesn't render failures list when there are no failures`, () => { const shardFailures = []; - const component = renderWithIntl( - ''} - /> - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); @@ -64,12 +73,7 @@ describe('DiscoverNoResults', () => { describe('timeFieldName', () => { test('renders time range feedback', () => { - const component = renderWithIntl( - ''} - /> - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); @@ -78,10 +82,7 @@ describe('DiscoverNoResults', () => { describe('queryLanguage', () => { test('supports lucene and renders doc link', () => { const component = renderWithIntl( - 'documentation-link'} - /> + 'documentation-link'} /> ); expect(component).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js index ac26203dafc4ad..b1c1c47e39291d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js @@ -18,7 +18,6 @@ */ import React, { Fragment } from 'react'; - import { EuiCallOut, EuiFlexGroup, diff --git a/src/legacy/core_plugins/kibana/public/discover/index.html b/src/legacy/core_plugins/kibana/public/discover/angular/discover.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/index.html rename to src/legacy/core_plugins/kibana/public/discover/angular/discover.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 840152fc40ced3..ed5049aa912e0a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -18,63 +18,65 @@ */ import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import React from 'react'; -import angular from 'angular'; import { Subscription } from 'rxjs'; import moment from 'moment'; -import chrome from 'ui/chrome'; import dateMath from '@elastic/datemath'; +import { i18n } from '@kbn/i18n'; +import '../saved_searches/saved_searches'; +import '../components/field_chooser/field_chooser'; // doc table -import '../doc_table'; -import { getSort } from '../doc_table/lib/get_sort'; -import { getSortForSearchSource } from '../doc_table/lib/get_sort_for_search_source'; -import * as columnActions from '../doc_table/actions/columns'; -import * as filterActions from '../doc_table/actions/filter'; - -import 'ui/directives/listen'; -import 'ui/visualize'; -import 'ui/fixed_scroll'; -import 'ui/index_patterns'; -import 'ui/state_management/app_state'; -import { timefilter } from 'ui/timefilter'; -import { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier'; -import { toastNotifications } from 'ui/notify'; -import { VisProvider } from 'ui/vis'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; -import { docTitle } from 'ui/doc_title'; -import { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; -import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; -import uiRoutes from 'ui/routes'; -import { uiModules } from 'ui/modules'; -import indexTemplate from '../index.html'; -import { StateProvider } from 'ui/state_management/state'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { getFilterGenerator } from 'ui/filter_manager'; - -import { getDocLink } from 'ui/documentation_links'; -import '../components/fetch_error'; -import { getPainlessError } from './get_painless_error'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; -import { Inspector } from 'ui/inspector'; -import { RequestAdapter } from 'ui/inspector/adapters'; -import { getRequestInspectorStats, getResponseInspectorStats } from 'ui/courier/utils/courier_inspector_utils'; +import './doc_table'; +import { getSort } from './doc_table/lib/get_sort'; +import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source'; +import * as columnActions from './doc_table/actions/columns'; +import * as filterActions from './doc_table/actions/filter'; + +import indexTemplate from './discover.html'; import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; -import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; -import { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; -import 'ui/capabilities/route_setup'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; +import '../components/fetch_error'; +import { getPainlessError } from './get_painless_error'; +import { + angular, + buildVislibDimensions, + getFilterGenerator, + getRequestInspectorStats, + getResponseInspectorStats, + getServices, + getUnhashableStatesProvider, + hasSearchStategyForIndexPattern, + intervalOptions, + isDefaultTypeIndexPattern, + migrateLegacyQuery, + RequestAdapter, + showSaveModal, + showShareContextMenu, + stateMonitorFactory, + subscribeWithScope, + tabifyAggResponse, + vislibSeriesResponseHandlerProvider, + VisProvider, + SavedObjectSaveModal, +} from '../kibana_services'; + +const { + chrome, + docTitle, + FilterBarQueryFilterProvider, + ShareContextMenuExtensionsRegistryProvider, + StateProvider, + timefilter, + toastNotifications, + uiModules, + uiRoutes, +} = getServices(); +import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; -import { npStart } from 'ui/new_platform'; + const { savedQueryService } = data.search.services; @@ -151,7 +153,7 @@ uiRoutes return savedSearches.get(savedSearchId) .then((savedSearch) => { if (savedSearchId) { - npStart.core.chrome.recentlyAccessed.add( + chrome.recentlyAccessed.add( savedSearch.getFullPath(), savedSearch.title, savedSearchId); @@ -211,8 +213,6 @@ function discoverController( mode: 'absolute', }); }; - - $scope.getDocLink = getDocLink; $scope.intervalOptions = intervalOptions; $scope.showInterval = false; $scope.minimumVisibleRows = 50; @@ -354,7 +354,7 @@ function discoverController( }), testId: 'openInspectorButton', run() { - Inspector.open(inspectorAdapters, { + getServices().inspector.open(inspectorAdapters, { title: savedSearch.title }); } @@ -401,12 +401,12 @@ function discoverController( }); if (savedSearch.id && savedSearch.title) { - chrome.breadcrumbs.set([{ + chrome.setBreadcrumbs([{ text: discoverBreadcrumbsTitle, href: '#/discover', }, { text: savedSearch.title }]); } else { - chrome.breadcrumbs.set([{ + chrome.setBreadcrumbs([{ text: discoverBreadcrumbsTitle, }]); } diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/index.html b/src/legacy/core_plugins/kibana/public/discover/angular/doc.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc/index.html rename to src/legacy/core_plugins/kibana/public/discover/angular/doc.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts new file mode 100644 index 00000000000000..e6c890c9a66a22 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts @@ -0,0 +1,63 @@ +/* + * 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 { getServices, IndexPatterns } from '../kibana_services'; +// @ts-ignore +import { getRootBreadcrumbs } from '../breadcrumbs'; +import html from './doc.html'; +import { Doc } from '../doc/doc'; +const { uiRoutes, uiModules, wrapInI18nContext, timefilter } = getServices(); +uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: any) { + return reactDirective( + wrapInI18nContext(Doc), + [ + ['id', { watchDepth: 'value' }], + ['index', { watchDepth: 'value' }], + ['indexPatternId', { watchDepth: 'reference' }], + ['indexPatternService', { watchDepth: 'reference' }], + ['esClient', { watchDepth: 'reference' }], + ], + { restrict: 'E' } + ); +}); + +uiRoutes + // the old, pre 8.0 route, no longer used, keep it to stay compatible + // somebody might have bookmarked his favorite log messages + .when('/doc/:indexPattern/:index/:type', { + redirectTo: '/doc/:indexPattern/:index', + }) + // the new route, es 7 deprecated types, es 8 removed them + .when('/doc/:indexPattern/:index', { + controller: ($scope: any, $route: any, es: any, indexPatterns: IndexPatterns) => { + timefilter.disableAutoRefreshSelector(); + timefilter.disableTimeRangeSelector(); + $scope.esClient = es; + $scope.id = $route.current.params.id; + $scope.index = $route.current.params.index; + $scope.indexPatternId = $route.current.params.indexPattern; + $scope.indexPatternService = indexPatterns; + }, + template: html, + k7Breadcrumbs: ($route: any) => [ + ...getRootBreadcrumbs(), + { + text: `${$route.current.params.index}#${$route.current.params.id}`, + }, + ], + }); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/actions/filter.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/get_sort.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/get_sort.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/rows_headers.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/rows_headers.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/_doc_table.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/_doc_table.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/actions/columns.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/columns.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/actions/columns.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/columns.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/actions/filter.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/_table_header.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_table_header.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/_table_header.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_table_header.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js new file mode 100644 index 00000000000000..7462de544dbce3 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js @@ -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 { getServices } from '../../../../kibana_services'; +import { ToolBarPagerText } from './tool_bar_pager_text'; +import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; + +const { wrapInI18nContext, uiModules } = getServices(); + +const app = uiModules.get('kibana'); + +app.directive('toolBarPagerText', function (reactDirective) { + return reactDirective(wrapInI18nContext(ToolBarPagerText)); +}); + +app.directive('toolBarPagerButtons', function (reactDirective) { + return reactDirective(wrapInI18nContext(ToolBarPagerButtons)); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx index 6ce987028c197e..3edcda8c3bea99 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx @@ -34,7 +34,7 @@ export function ToolBarPagerButtons(props: Props) { disabled={!props.hasPreviousPage} data-test-subj="btnPrevPage" > - + ); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts index e054120c084749..f447c545077293 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts @@ -17,10 +17,9 @@ * under the License. */ import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; +import { getServices } from '../../../kibana_services'; import { TableHeader } from './table_header/table_header'; -const module = uiModules.get('app/discover'); +const module = getServices().uiModules.get('app/discover'); module.directive('kbnTableHeader', function(reactDirective: any, config: any) { return reactDirective( diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/helpers.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/helpers.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx index ddf960091d4761..80f963c8ccb3ee 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/helpers.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx @@ -16,10 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../kibana_services'; // @ts-ignore -import { shortenDottedString } from '../../../../../common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../../../../../common/utils/shorten_dotted_string'; export type SortOrder = [string, 'asc' | 'desc']; export interface ColumnProps { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx index ea2c65b1b84879..09ba77c7c49992 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx @@ -23,7 +23,7 @@ import { TableHeader } from './table_header'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; -import { IndexPattern, FieldType } from 'ui/index_patterns'; +import { IndexPattern, FieldType } from '../../../../kibana_services'; function getMockIndexPattern() { return ({ diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx similarity index 91% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx index abc8077afb6698..71674710ac8559 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx @@ -17,9 +17,8 @@ * under the License. */ import React from 'react'; -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../kibana_services'; // @ts-ignore -import { shortenDottedString } from '../../../../../common/utils/shorten_dotted_string'; import { TableHeaderColumn } from './table_header_column'; import { SortOrder, getDisplayedColumns } from './helpers'; @@ -48,7 +47,7 @@ export function TableHeader({ return ( - + {displayedColumns.map(col => { return ( { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts new file mode 100644 index 00000000000000..c13c3545284139 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.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. + */ + +// @ts-ignore +import { getServices } from '../kibana_services'; +import { DocViewer } from '../doc_viewer/doc_viewer'; + +const { uiModules } = getServices(); + +uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { + return reactDirective( + DocViewer, + [ + 'hit', + ['indexPattern', { watchDepth: 'reference' }], + ['filter', { watchDepth: 'reference' }], + ['columns', { watchDepth: 'collection' }], + ['onAddColumn', { watchDepth: 'reference' }], + ['onRemoveColumn', { watchDepth: 'reference' }], + ], + { + restrict: 'E', + scope: { + hit: '=', + indexPattern: '=', + filter: '=?', + columns: '=?', + onAddColumn: '=?', + onRemoveColumn: '=?', + }, + } + ); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/index.ts b/src/legacy/core_plugins/kibana/public/discover/angular/index.ts new file mode 100644 index 00000000000000..5bae0d9d551e53 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/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. + */ +import './discover'; +import './doc'; +import './context'; +import './doc_viewer'; +import './directives'; diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.js b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.js deleted file mode 100644 index 1220c99b5ee568..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.js +++ /dev/null @@ -1,40 +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'; - -export function getRootBreadcrumbs() { - return [ - { - text: i18n.translate('kbn.discover.rootBreadcrumb', { - defaultMessage: 'Discover' - }), - href: '#/discover' - } - ]; -} - -export function getSavedSearchBreadcrumbs($route) { - return [ - ...getRootBreadcrumbs(), - { - text: $route.current.locals.savedSearch.id, - } - ]; -} diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts new file mode 100644 index 00000000000000..51e0dcba1cad0b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts @@ -0,0 +1,40 @@ +/* + * 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'; + +export function getRootBreadcrumbs() { + return [ + { + text: i18n.translate('kbn.discover.rootBreadcrumb', { + defaultMessage: 'Discover', + }), + href: '#/discover', + }, + ]; +} + +export function getSavedSearchBreadcrumbs($route: any) { + return [ + ...getRootBreadcrumbs(), + { + text: $route.current.locals.savedSearch.id, + }, + ]; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js index 670e9446c6e4f9..612ca860f80317 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js @@ -16,21 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import 'ngreact'; import React, { Fragment } from 'react'; -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { npStart } from 'ui/new_platform'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiCallOut, - EuiCodeBlock, - EuiSpacer, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; +import { getServices } from '../../kibana_services'; +const { uiModules, wrapInI18nContext, chrome } = getServices(); const DiscoverFetchError = ({ fetchError }) => { if (!fetchError) { @@ -40,7 +30,7 @@ const DiscoverFetchError = ({ fetchError }) => { let body; if (fetchError.lang === 'painless') { - const managementUrl = npStart.core.chrome.navLinks.get('kibana:management').url; + const managementUrl = chrome.navLinks.get('kibana:management').url; const url = `${managementUrl}/kibana/index_patterns`; body = ( @@ -51,10 +41,12 @@ const DiscoverFetchError = ({ fetchError }) => { in {managementLink}, under the {scriptedFields} tab." values={{ fetchErrorScript: `'${fetchError.script}'`, - scriptedFields: , + scriptedFields: ( + + ), managementLink: ( { defaultMessage="Management > Index Patterns" /> - ) + ), }} />

@@ -75,16 +67,10 @@ const DiscoverFetchError = ({ fetchError }) => { - + {body} - - {fetchError.error} - + {fetchError.error} @@ -96,4 +82,6 @@ const DiscoverFetchError = ({ fetchError }) => { const app = uiModules.get('apps/discover', ['react']); -app.directive('discoverFetchError', reactDirective => reactDirective(wrapInI18nContext(DiscoverFetchError))); +app.directive('discoverFetchError', reactDirective => + reactDirective(wrapInI18nContext(DiscoverFetchError)) +); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss index 6960c7101fa10a..fe13ac2fafa01f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss @@ -27,3 +27,11 @@ padding-left: $euiSizeXS; margin-left: $euiSizeXS; } + +.dscFieldSearch__filterWrapper { + flex-grow: 0; +} + +.dscFieldSearch__formWrapper { + padding: $euiSizeM; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js index f7469d0142d57c..cfcb6540771523 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js @@ -18,15 +18,15 @@ */ import $ from 'jquery'; +import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { getServices } from '../../kibana_services'; import html from './discover_field.html'; -import _ from 'lodash'; import 'ui/directives/css_truncate'; import 'ui/directives/field_name'; import './string_progress_bar'; import detailsHtml from './lib/detail_views/string.html'; -import { capabilities } from 'ui/capabilities'; -import { uiModules } from 'ui/modules'; +const { uiModules, capabilities } = getServices(); const app = uiModules.get('apps/discover'); app.directive('discoverField', function ($compile) { @@ -78,7 +78,7 @@ app.directive('discoverField', function ($compile) { }; - $scope.canVisualize = capabilities.get().visualize.show; + $scope.canVisualize = capabilities.visualize.show; $scope.toggleDisplay = function (field) { if (field.display) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx index 2655d28af985bc..badfbb4b14a4c1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx @@ -16,55 +16,164 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { EventHandler, MouseEvent as ReactMouseEvent } from 'react'; +import { act } from 'react-dom/test-utils'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { DiscoverFieldSearch } from './discover_field_search'; +import { DiscoverFieldSearch, Props } from './discover_field_search'; +import { EuiButtonGroupProps, EuiPopover } from '@elastic/eui'; +import { ReactWrapper } from 'enzyme'; describe('DiscoverFieldSearch', () => { - function mountComponent() { - const props = { - onChange: jest.fn(), - onShowFilter: jest.fn(), - showFilter: false, - filtersActive: 0, - value: 'test', - }; - const comp = mountWithIntl(); - const input = findTestSubject(comp, 'fieldFilterSearchInput'); - const btn = findTestSubject(comp, 'toggleFieldFilterButton'); - return { comp, input, btn, props }; + const defaultProps = { + onChange: jest.fn(), + value: 'test', + types: ['any', 'string', '_source'], + }; + + function mountComponent(props?: Props) { + const compProps = props || defaultProps; + const comp = mountWithIntl(); + return comp; + } + + function findButtonGroup(component: ReactWrapper, id: string) { + return component.find(`[data-test-subj="${id}ButtonGroup"]`).first(); } test('enter value', () => { - const { input, props } = mountComponent(); + const component = mountComponent(); + const input = findTestSubject(component, 'fieldFilterSearchInput'); input.simulate('change', { target: { value: 'new filter' } }); - expect(props.onChange).toBeCalledTimes(1); + expect(defaultProps.onChange).toBeCalledTimes(1); }); - // this should work, but doesn't, have to do some research - test('click toggle filter button', () => { - const { btn, props } = mountComponent(); + test('change in active filters should change facet selection and call onChange', () => { + const onChange = jest.fn(); + const component = mountComponent({ ...defaultProps, ...{ onChange } }); + let btn = findTestSubject(component, 'toggleFieldFilterButton'); + expect(btn.hasClass('euiFacetButton--isSelected')).toBeFalsy(); btn.simulate('click'); - expect(props.onShowFilter).toBeCalledTimes(1); + const aggregatableButtonGroup = findButtonGroup(component, 'aggregatable'); + act(() => { + // @ts-ignore + (aggregatableButtonGroup.props() as EuiButtonGroupProps).onChange('aggregatable-true', null); + }); + component.update(); + btn = findTestSubject(component, 'toggleFieldFilterButton'); + expect(btn.hasClass('euiFacetButton--isSelected')).toBe(true); + expect(onChange).toBeCalledWith('aggregatable', true); }); - test('change showFilter value should change aria label', () => { - const { comp } = mountComponent(); - let btn = findTestSubject(comp, 'toggleFieldFilterButton'); - expect(btn.prop('aria-label')).toEqual('Show field filter settings'); - comp.setProps({ showFilter: true }); - btn = findTestSubject(comp, 'toggleFieldFilterButton'); - expect(btn.prop('aria-label')).toEqual('Hide field filter settings'); + test('change in active filters should change filters count', () => { + const component = mountComponent(); + let btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + btn = findTestSubject(component, 'toggleFieldFilterButton'); + const badge = btn.find('.euiNotificationBadge'); + // no active filters + expect(badge.text()).toEqual('0'); + // change value of aggregatable select + const aggregatableButtonGroup = findButtonGroup(component, 'aggregatable'); + act(() => { + // @ts-ignore + (aggregatableButtonGroup.props() as EuiButtonGroupProps).onChange('aggregatable-true', null); + }); + component.update(); + expect(badge.text()).toEqual('1'); + // change value of searchable select + const searchableButtonGroup = findButtonGroup(component, 'searchable'); + act(() => { + // @ts-ignore + (searchableButtonGroup.props() as EuiButtonGroupProps).onChange('searchable-true', null); + }); + component.update(); + expect(badge.text()).toEqual('2'); + // change value of searchable select + act(() => { + // @ts-ignore + (searchableButtonGroup.props() as EuiButtonGroupProps).onChange('searchable-any', null); + }); + component.update(); + expect(badge.text()).toEqual('1'); }); - test('change filtersActive should change facet selection', () => { - const { comp } = mountComponent(); - let btn = findTestSubject(comp, 'toggleFieldFilterButton'); - expect(btn.hasClass('euiFacetButton--isSelected')).toBeFalsy(); - comp.setProps({ filtersActive: 3 }); - btn = findTestSubject(comp, 'toggleFieldFilterButton'); - expect(btn.hasClass('euiFacetButton--isSelected')).toBe(true); + test('change in missing fields switch should not change filter count', () => { + const component = mountComponent(); + const btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + const badge = btn.find('.euiNotificationBadge'); + expect(badge.text()).toEqual('0'); + const missingSwitch = findTestSubject(component, 'missingSwitch'); + missingSwitch.simulate('change', { target: { value: false } }); + expect(badge.text()).toEqual('0'); + }); + + test('change in filters triggers onChange', () => { + const onChange = jest.fn(); + const component = mountComponent({ ...defaultProps, ...{ onChange } }); + const btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + const aggregtableButtonGroup = findButtonGroup(component, 'aggregatable'); + const missingSwitch = findTestSubject(component, 'missingSwitch'); + act(() => { + // @ts-ignore + (aggregtableButtonGroup.props() as EuiButtonGroupProps).onChange('aggregatable-true', null); + }); + missingSwitch.simulate('change', { target: { value: false } }); + expect(onChange).toBeCalledTimes(2); + }); + + test('change in type filters triggers onChange with appropriate value', () => { + const onChange = jest.fn(); + const component = mountComponent({ ...defaultProps, ...{ onChange } }); + const btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + const typeSelector = findTestSubject(component, 'typeSelect'); + typeSelector.simulate('change', { target: { value: 'string' } }); + expect(onChange).toBeCalledWith('type', 'string'); + typeSelector.simulate('change', { target: { value: 'any' } }); + expect(onChange).toBeCalledWith('type', 'any'); + }); + + test('click on filter button should open and close popover', () => { + const component = mountComponent(); + const btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + let popover = component.find(EuiPopover); + expect(popover.prop('isOpen')).toBe(true); + btn.simulate('click'); + popover = component.find(EuiPopover); + expect(popover.prop('isOpen')).toBe(false); + }); + + test('click outside popover should close popover', () => { + const triggerDocumentMouseDown: EventHandler = (e: ReactMouseEvent) => { + const event = new Event('mousedown'); + // @ts-ignore + event.euiGeneratedBy = e.nativeEvent.euiGeneratedBy; + document.dispatchEvent(event); + }; + const triggerDocumentMouseUp: EventHandler = (e: ReactMouseEvent) => { + const event = new Event('mouseup'); + // @ts-ignore + event.euiGeneratedBy = e.nativeEvent.euiGeneratedBy; + document.dispatchEvent(event); + }; + const component = mountWithIntl( +
+ +
+ ); + const btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + let popover = component.find(EuiPopover); + expect(popover.length).toBe(1); + expect(popover.prop('isOpen')).toBe(true); + component.find('#wrapperId').simulate('mousedown'); + component.find('#wrapperId').simulate('mouseup'); + popover = component.find(EuiPopover); + expect(popover.prop('isOpen')).toBe(false); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx index f748cdae1b4fcb..3d93487d9e6ccd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx @@ -16,60 +16,235 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { OptionHTMLAttributes, ReactNode, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFacetButton, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { + EuiFacetButton, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPopover, + EuiPopoverFooter, + EuiPopoverTitle, + EuiSelect, + EuiSwitch, + EuiForm, + EuiFormRow, + EuiButtonGroup, + EuiOutsideClickDetector, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +export interface State { + searchable: string; + aggregatable: string; + type: string; + missing: boolean; + [index: string]: string | boolean; +} + export interface Props { /** * triggered on input of user into search field */ - onChange: (field: string, value: string) => void; - /** - * triggered when the "additional filter btn" is clicked - */ - onShowFilter: () => void; - /** - * determines whether additional filter fields are displayed - */ - showFilter: boolean; + onChange: (field: string, value: string | boolean | undefined) => void; + /** * the input value of the user */ value?: string; + /** - * the number of selected filters + * types for the type filter */ - filtersActive: number; + types: string[]; } /** * Component is Discover's side bar to search of available fields * Additionally there's a button displayed that allows the user to show/hide more filter fields */ -export function DiscoverFieldSearch({ - showFilter, - onChange, - onShowFilter, - value, - filtersActive, -}: Props) { +export function DiscoverFieldSearch({ onChange, value, types }: Props) { if (typeof value !== 'string') { // at initial rendering value is undefined (angular related), this catches the warning // should be removed once all is react return null; } - const filterBtnAriaLabel = showFilter + const searchPlaceholder = i18n.translate('kbn.discover.fieldChooser.searchPlaceHolder', { + defaultMessage: 'Search field names', + }); + const aggregatableLabel = i18n.translate('kbn.discover.fieldChooser.filter.aggregatableLabel', { + defaultMessage: 'Aggregatable', + }); + const searchableLabel = i18n.translate('kbn.discover.fieldChooser.filter.searchableLabel', { + defaultMessage: 'Searchable', + }); + const typeLabel = i18n.translate('kbn.discover.fieldChooser.filter.typeLabel', { + defaultMessage: 'Type', + }); + const typeOptions = types + ? types.map(type => { + return { value: type, text: type }; + }) + : [{ value: 'any', text: 'any' }]; + + const [activeFiltersCount, setActiveFiltersCount] = useState(0); + const [isPopoverOpen, setPopoverOpen] = useState(false); + const [values, setValues] = useState({ + searchable: 'any', + aggregatable: 'any', + type: 'any', + missing: true, + }); + + const filterBtnAriaLabel = isPopoverOpen ? i18n.translate('kbn.discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel', { defaultMessage: 'Hide field filter settings', }) : i18n.translate('kbn.discover.fieldChooser.toggleFieldFilterButtonShowAriaLabel', { defaultMessage: 'Show field filter settings', }); - const searchPlaceholder = i18n.translate('kbn.discover.fieldChooser.searchPlaceHolder', { - defaultMessage: 'Search fields', - }); + + const handleFacetButtonClicked = () => { + setPopoverOpen(!isPopoverOpen); + }; + + const applyFilterValue = (id: string, filterValue: string | boolean) => { + switch (filterValue) { + case 'any': + if (id !== 'type') { + onChange(id, undefined); + } else { + onChange(id, filterValue); + } + break; + case 'true': + onChange(id, true); + break; + case 'false': + onChange(id, false); + break; + default: + onChange(id, filterValue); + } + }; + + const isFilterActive = (name: string, filterValue: string | boolean) => { + return name !== 'missing' && filterValue !== 'any'; + }; + + const handleValueChange = (name: string, filterValue: string | boolean) => { + const previousValue = values[name]; + updateFilterCount(name, previousValue, filterValue); + const updatedValues = { ...values }; + updatedValues[name] = filterValue; + setValues(updatedValues); + applyFilterValue(name, filterValue); + }; + + const updateFilterCount = ( + name: string, + previousValue: string | boolean, + currentValue: string | boolean + ) => { + const previouslyFilterActive = isFilterActive(name, previousValue); + const filterActive = isFilterActive(name, currentValue); + const diff = Number(filterActive) - Number(previouslyFilterActive); + setActiveFiltersCount(activeFiltersCount + diff); + }; + + const handleMissingChange = (e: React.ChangeEvent) => { + const missingValue = e.target.checked; + handleValueChange('missing', missingValue); + }; + + const buttonContent = ( + } + isSelected={activeFiltersCount > 0} + quantity={activeFiltersCount} + onClick={handleFacetButtonClicked} + > + + + ); + + const select = ( + id: string, + selectOptions: Array<{ text: ReactNode } & OptionHTMLAttributes>, + selectValue: string + ) => { + return ( + ) => + handleValueChange(id, e.target.value) + } + aria-label={i18n.translate('kbn.discover.fieldChooser.filter.fieldSelectorLabel', { + defaultMessage: 'Selection of {id} filter options', + values: { id }, + })} + data-test-subj={`${id}Select`} + compressed + /> + ); + }; + + const toggleButtons = (id: string) => { + return [ + { + id: `${id}-any`, + label: 'any', + }, + { + id: `${id}-true`, + label: 'yes', + }, + { + id: `${id}-false`, + label: 'no', + }, + ]; + }; + + const buttonGroup = (id: string, legend: string) => { + return ( + handleValueChange(id, optionId.replace(`${id}-`, ''))} + buttonSize="compressed" + isFullWidth + data-test-subj={`${id}ButtonGroup`} + /> + ); + }; + + const selectionPanel = ( +
+ + + {buttonGroup('aggregatable', aggregatableLabel)} + + + {buttonGroup('searchable', searchableLabel)} + + + {select('type', typeOptions, values.type)} + + +
+ ); return ( @@ -86,20 +261,39 @@ export function DiscoverFieldSearch({ /> - } - isSelected={filtersActive > 0} - quantity={filtersActive} - onClick={() => onShowFilter()} - > - - +
+ {}} isDisabled={!isPopoverOpen}> + { + setPopoverOpen(false); + }} + button={buttonContent} + > + + {i18n.translate('kbn.discover.fieldChooser.filter.filterByTypeLabel', { + defaultMessage: 'Filter by type', + })} + + {selectionPanel} + + + + + +
); } diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts index 8af23caedd78ae..b78f993e187725 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts @@ -17,18 +17,17 @@ * under the License. */ // @ts-ignore -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; +import { getServices } from '../../kibana_services'; import { DiscoverFieldSearch } from './discover_field_search'; +const { wrapInI18nContext, uiModules } = getServices(); + const app = uiModules.get('apps/discover'); app.directive('discoverFieldSearch', function(reactDirective: any) { return reactDirective(wrapInI18nContext(DiscoverFieldSearch), [ ['onChange', { watchDepth: 'reference' }], - ['onShowFilter', { watchDepth: 'reference' }], - ['showFilter', { watchDepth: 'value' }], ['value', { watchDepth: 'value' }], - ['filtersActive', { watchDepth: 'value' }], + ['types', { watchDepth: 'value' }], ]); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts index 938d6cc226f2f3..5e3f678e388ad3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts @@ -17,10 +17,11 @@ * under the License. */ // @ts-ignore -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; +import { getServices } from '../../kibana_services'; import { DiscoverIndexPattern } from './discover_index_pattern'; +const { wrapInI18nContext, uiModules } = getServices(); + const app = uiModules.get('apps/discover'); app.directive('discoverIndexPatternSelect', function(reactDirective: any) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html index 3d8b38c278f318..adf4b1b4326e88 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html @@ -9,10 +9,8 @@
diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js index 3e0172dec1ff42..99a63efc0e0fca 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js @@ -16,20 +16,22 @@ * specific language governing permissions and limitations * under the License. */ - -import 'ui/directives/css_truncate'; +//field_name directive will be replaced very soon import 'ui/directives/field_name'; import './discover_field'; -import 'ui/angular_ui_select'; import './discover_field_search_directive'; import './discover_index_pattern_directive'; import _ from 'lodash'; import $ from 'jquery'; import rison from 'rison-node'; import { fieldCalculator } from './lib/field_calculator'; -import { FieldList } from 'ui/index_patterns'; -import { uiModules } from 'ui/modules'; +import { + getServices, + FieldList +} from '../../kibana_services'; import fieldChooserTemplate from './field_chooser.html'; + +const { uiModules } = getServices(); const app = uiModules.get('apps/discover'); app.directive('discFieldChooser', function ($location, config, $route) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js index ae00df6dfbbf8a..ca3a47cad50757 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js @@ -16,13 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - - -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; - import React from 'react'; - +import { getServices } from '../../kibana_services'; import { EuiFlexGroup, EuiFlexItem, @@ -31,6 +26,7 @@ import { EuiToolTip, } from '@elastic/eui'; +const { wrapInI18nContext, uiModules } = getServices(); const module = uiModules.get('discover/field_chooser'); function StringFieldProgressBar(props) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js index 95db1b686f7aaa..ad68e55e71622c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js @@ -20,7 +20,8 @@ import React, { Fragment, PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { getServices } from '../../kibana_services'; +const { docLinks } = getServices(); export class HelpMenu extends PureComponent { render() { @@ -31,7 +32,7 @@ export class HelpMenu extends PureComponent { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js index aeabff2d97007b..58a92193de63e8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { + chrome.setHelpExtension(domElement => { render(, domElement); return () => { unmountComponentAtNode(domElement); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/README.md b/src/legacy/core_plugins/kibana/public/discover/context/README.md new file mode 100644 index 00000000000000..18ba118b4da798 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/context/README.md @@ -0,0 +1,4 @@ +# DISCOVER CONTEXT + +Placeholder for Discover's context functionality, that's currently in [../angular/context](../angular/context). +Once fully de-angularized it should be moved to this location \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/discover/context/app.js b/src/legacy/core_plugins/kibana/public/discover/context/app.js deleted file mode 100644 index 7754f743632cb8..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/context/app.js +++ /dev/null @@ -1,156 +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 _ from 'lodash'; - -import { callAfterBindingsWorkaround } from 'ui/compat'; -import { uiModules } from 'ui/modules'; -import contextAppTemplate from './app.html'; -import './components/action_bar'; -import { getFirstSortableField } from './api/utils/sorting'; -import { - createInitialQueryParametersState, - QueryParameterActionsProvider, - QUERY_PARAMETER_KEYS, -} from './query_parameters'; -import { - createInitialLoadingStatusState, - FAILURE_REASONS, - LOADING_STATUS, - QueryActionsProvider, -} from './query'; -import { timefilter } from 'ui/timefilter'; - -// load directives -import '../../../../data/public/legacy'; - -const module = uiModules.get('apps/context', [ - 'elasticsearch', - 'kibana', - 'kibana/config', - 'ngRoute', -]); - -module.directive('contextApp', function ContextApp() { - return { - bindToController: true, - controller: callAfterBindingsWorkaround(ContextAppController), - controllerAs: 'contextApp', - restrict: 'E', - scope: { - anchorId: '=', - columns: '=', - indexPattern: '=', - filters: '=', - predecessorCount: '=', - successorCount: '=', - sort: '=', - discoverUrl: '=', - }, - template: contextAppTemplate, - }; -}); - -function ContextAppController($scope, config, Private) { - const queryParameterActions = Private(QueryParameterActionsProvider); - const queryActions = Private(QueryActionsProvider); - - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - - this.state = createInitialState( - parseInt(config.get('context:step'), 10), - getFirstSortableField(this.indexPattern, config.get('context:tieBreakerFields')), - this.discoverUrl, - ); - - this.actions = _.mapValues({ - ...queryParameterActions, - ...queryActions, - }, (action) => (...args) => action(this.state)(...args)); - - this.constants = { - FAILURE_REASONS, - LOADING_STATUS, - }; - - $scope.$watchGroup([ - () => this.state.rows.predecessors, - () => this.state.rows.anchor, - () => this.state.rows.successors, - ], (newValues) => this.actions.setAllRows(...newValues)); - - /** - * Sync properties to state - */ - $scope.$watchCollection( - () => ({ - ...(_.pick(this, QUERY_PARAMETER_KEYS)), - indexPatternId: this.indexPattern.id, - }), - (newQueryParameters) => { - const { queryParameters } = this.state; - if ( - (newQueryParameters.indexPatternId !== queryParameters.indexPatternId) - || (newQueryParameters.anchorId !== queryParameters.anchorId) - || (!_.isEqual(newQueryParameters.sort, queryParameters.sort)) - ) { - this.actions.fetchAllRowsWithNewQueryParameters(_.cloneDeep(newQueryParameters)); - } else if ( - (newQueryParameters.predecessorCount !== queryParameters.predecessorCount) - || (newQueryParameters.successorCount !== queryParameters.successorCount) - || (!_.isEqual(newQueryParameters.filters, queryParameters.filters)) - ) { - this.actions.fetchContextRowsWithNewQueryParameters(_.cloneDeep(newQueryParameters)); - } - }, - ); - - /** - * Sync state to properties - */ - $scope.$watchCollection( - () => ({ - predecessorCount: this.state.queryParameters.predecessorCount, - successorCount: this.state.queryParameters.successorCount, - }), - (newParameters) => { - _.assign(this, newParameters); - }, - ); -} - - -function createInitialState(defaultStepSize, tieBreakerField, discoverUrl) { - return { - queryParameters: createInitialQueryParametersState(defaultStepSize, tieBreakerField), - rows: { - all: [], - anchor: null, - predecessors: [], - successors: [], - }, - loadingStatus: createInitialLoadingStatusState(), - navigation: { - discover: { - url: discoverUrl, - }, - }, - }; -} diff --git a/src/legacy/core_plugins/kibana/public/discover/context/index.js b/src/legacy/core_plugins/kibana/public/discover/context/index.js deleted file mode 100644 index 902bee2badb7cc..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/context/index.js +++ /dev/null @@ -1,107 +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 _ from 'lodash'; - -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import uiRoutes from 'ui/routes'; -import { i18n } from '@kbn/i18n'; - -import './app'; -import contextAppRouteTemplate from './index.html'; -import { getRootBreadcrumbs } from '../breadcrumbs'; -import { npStart } from 'ui/new_platform'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; - -const k7Breadcrumbs = $route => { - const { indexPattern } = $route.current.locals; - const { id } = $route.current.params; - - return [ - ...getRootBreadcrumbs(), - { - text: i18n.translate('kbn.context.breadcrumb', { - defaultMessage: 'Context of {indexPatternTitle}#{docId}', - values: { - indexPatternTitle: indexPattern.title, - docId: id, - }, - }), - }, - ]; -}; - - -uiRoutes - // deprecated route, kept for compatibility - // should be removed in the future - .when('/context/:indexPatternId/:type/:id*', { - redirectTo: '/context/:indexPatternId/:id' - }) - .when('/context/:indexPatternId/:id*', { - controller: ContextAppRouteController, - k7Breadcrumbs, - controllerAs: 'contextAppRoute', - resolve: { - indexPattern: function ($route, indexPatterns) { - return indexPatterns.get($route.current.params.indexPatternId); - }, - }, - template: contextAppRouteTemplate, - }); - -function ContextAppRouteController($routeParams, $scope, AppState, config, indexPattern, Private) { - const queryFilter = Private(FilterBarQueryFilterProvider); - - this.state = new AppState(createDefaultAppState(config, indexPattern)); - this.state.save(true); - - $scope.$watchGroup( - [ - 'contextAppRoute.state.columns', - 'contextAppRoute.state.predecessorCount', - 'contextAppRoute.state.successorCount', - ], - () => this.state.save(true) - ); - - const updateSubsciption = subscribeWithScope($scope, queryFilter.getUpdates$(), { - next: () => { - this.filters = _.cloneDeep(queryFilter.getFilters()); - }, - }); - - $scope.$on('$destroy', function () { - updateSubsciption.unsubscribe(); - }); - this.anchorId = $routeParams.id; - this.indexPattern = indexPattern; - this.discoverUrl = npStart.core.chrome.navLinks.get('kibana:discover').url; - this.filters = _.cloneDeep(queryFilter.getFilters()); -} - -function createDefaultAppState(config, indexPattern) { - return { - columns: ['_source'], - filters: [], - predecessorCount: parseInt(config.get('context:defaultSize'), 10), - sort: [indexPattern.timeFieldName, 'desc'], - successorCount: parseInt(config.get('context:defaultSize'), 10), - }; -} diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js deleted file mode 100644 index c55dcc374fa5ad..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js +++ /dev/null @@ -1,192 +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 _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { toastNotifications } from 'ui/notify'; - -import { fetchAnchorProvider } from '../api/anchor'; -import { fetchContextProvider } from '../api/context'; -import { QueryParameterActionsProvider } from '../query_parameters'; -import { FAILURE_REASONS, LOADING_STATUS } from './constants'; -import { MarkdownSimple } from '../../../../../kibana_react/public'; - -export function QueryActionsProvider(Private, Promise) { - const fetchAnchor = Private(fetchAnchorProvider); - const { fetchSurroundingDocs } = Private(fetchContextProvider); - const { - setPredecessorCount, - setQueryParameters, - setSuccessorCount, - } = Private(QueryParameterActionsProvider); - - const setFailedStatus = (state) => (subject, details = {}) => ( - state.loadingStatus[subject] = { - status: LOADING_STATUS.FAILED, - reason: FAILURE_REASONS.UNKNOWN, - ...details, - } - ); - - const setLoadedStatus = (state) => (subject) => ( - state.loadingStatus[subject] = { - status: LOADING_STATUS.LOADED, - } - ); - - const setLoadingStatus = (state) => (subject) => ( - state.loadingStatus[subject] = { - status: LOADING_STATUS.LOADING, - } - ); - - const fetchAnchorRow = (state) => () => { - const { queryParameters: { indexPatternId, anchorId, sort, tieBreakerField } } = state; - - if (!tieBreakerField) { - return Promise.reject(setFailedStatus(state)('anchor', { - reason: FAILURE_REASONS.INVALID_TIEBREAKER - })); - } - - setLoadingStatus(state)('anchor'); - - return Promise.try(() => ( - fetchAnchor(indexPatternId, anchorId, [_.zipObject([sort]), { [tieBreakerField]: sort[1] }]) - )) - .then( - (anchorDocument) => { - setLoadedStatus(state)('anchor'); - state.rows.anchor = anchorDocument; - return anchorDocument; - }, - (error) => { - setFailedStatus(state)('anchor', { error }); - toastNotifications.addDanger({ - title: i18n.translate('kbn.context.unableToLoadAnchorDocumentDescription', { - defaultMessage: 'Unable to load the anchor document' - }), - text: {error.message}, - }); - throw error; - } - ); - }; - - const fetchSurroundingRows = (type, state) => { - const { - queryParameters: { indexPatternId, filters, sort, tieBreakerField }, - rows: { anchor }, - } = state; - const count = type === 'successors' - ? state.queryParameters.successorCount - : state.queryParameters.predecessorCount; - - if (!tieBreakerField) { - return Promise.reject(setFailedStatus(state)(type, { - reason: FAILURE_REASONS.INVALID_TIEBREAKER - })); - } - - setLoadingStatus(state)(type); - const [sortField, sortDir] = sort; - - return Promise.try(() => ( - fetchSurroundingDocs( - type, - indexPatternId, - anchor, - sortField, - tieBreakerField, - sortDir, - count, - filters - ) - )) - .then( - (documents) => { - setLoadedStatus(state)(type); - state.rows[type] = documents; - return documents; - }, - (error) => { - setFailedStatus(state)(type, { error }); - toastNotifications.addDanger({ - title: i18n.translate('kbn.context.unableToLoadDocumentDescription', { - defaultMessage: 'Unable to load documents' - }), - text: {error.message}, - }); - throw error; - }, - ); - }; - - const fetchContextRows = (state) => () => ( - Promise.all([ - fetchSurroundingRows('predecessors', state), - fetchSurroundingRows('successors', state), - ]) - ); - - const fetchAllRows = (state) => () => ( - Promise.try(fetchAnchorRow(state)) - .then(fetchContextRows(state)) - ); - - const fetchContextRowsWithNewQueryParameters = (state) => (queryParameters) => { - setQueryParameters(state)(queryParameters); - return fetchContextRows(state)(); - }; - - const fetchAllRowsWithNewQueryParameters = (state) => (queryParameters) => { - setQueryParameters(state)(queryParameters); - return fetchAllRows(state)(); - }; - - const fetchGivenPredecessorRows = (state) => (count) => { - setPredecessorCount(state)(count); - return fetchSurroundingRows('predecessors', state); - }; - - const fetchGivenSuccessorRows = (state) => (count) => { - setSuccessorCount(state)(count); - return fetchSurroundingRows('successors', state); - }; - - const setAllRows = (state) => (predecessorRows, anchorRow, successorRows) => ( - state.rows.all = [ - ...(predecessorRows || []), - ...(anchorRow ? [anchorRow] : []), - ...(successorRows || []), - ] - ); - - return { - fetchAllRows, - fetchAllRowsWithNewQueryParameters, - fetchAnchorRow, - fetchContextRows, - fetchContextRowsWithNewQueryParameters, - fetchGivenPredecessorRows, - fetchGivenSuccessorRows, - setAllRows, - }; -} diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js deleted file mode 100644 index 1c895b8d9e1c5c..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js +++ /dev/null @@ -1,81 +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 _ from 'lodash'; - -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { getFilterGenerator } from 'ui/filter_manager'; -import { - MAX_CONTEXT_SIZE, - MIN_CONTEXT_SIZE, - QUERY_PARAMETER_KEYS, -} from './constants'; - - -export function QueryParameterActionsProvider(indexPatterns, Private) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const filterGen = getFilterGenerator(queryFilter); - - const setPredecessorCount = (state) => (predecessorCount) => ( - state.queryParameters.predecessorCount = clamp( - MIN_CONTEXT_SIZE, - MAX_CONTEXT_SIZE, - predecessorCount, - ) - ); - - const setSuccessorCount = (state) => (successorCount) => ( - state.queryParameters.successorCount = clamp( - MIN_CONTEXT_SIZE, - MAX_CONTEXT_SIZE, - successorCount, - ) - ); - - const setQueryParameters = (state) => (queryParameters) => ( - Object.assign( - state.queryParameters, - _.pick(queryParameters, QUERY_PARAMETER_KEYS), - ) - ); - - const updateFilters = () => filters => { - queryFilter.setFilters(filters); - }; - - const addFilter = (state) => async (field, values, operation) => { - const indexPatternId = state.queryParameters.indexPatternId; - const newFilters = filterGen.generate(field, values, operation, indexPatternId); - queryFilter.addFilters(newFilters); - const indexPattern = await indexPatterns.get(indexPatternId); - indexPattern.popularizeField(field.name, 1); - }; - - return { - addFilter, - updateFilters, - setPredecessorCount, - setQueryParameters, - setSuccessorCount, - }; -} - -function clamp(minimum, maximum, value) { - return Math.max(Math.min(maximum, value), minimum); -} diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx index 6612097620b44b..b3efd23ea48d03 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx @@ -24,6 +24,27 @@ import { ReactWrapper } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; +jest.mock('../doc_viewer/doc_viewer', () => ({ + DocViewer: 'test', +})); + +jest.mock('../kibana_services', () => { + return { + getServices: () => ({ + metadata: { + branch: 'test', + }, + getDocViewsSorted: () => { + return []; + }, + }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); + // Suppress warnings about "act" until we use React 16.9 /* eslint-disable no-console */ const originalError = console.error; @@ -68,30 +89,30 @@ async function mountDoc(search: () => void, update = false, indexPatternGetter: } describe('Test of of Discover', () => { - it('renders loading msg', async () => { + test('renders loading msg', async () => { const comp = await mountDoc(jest.fn()); expect(findTestSubject(comp, 'doc-msg-loading').length).toBe(1); }); - it('renders IndexPattern notFound msg', async () => { + test('renders IndexPattern notFound msg', async () => { const indexPatternGetter = jest.fn(() => Promise.reject({ savedObjectId: '007' })); const comp = await mountDoc(jest.fn(), true, indexPatternGetter); expect(findTestSubject(comp, 'doc-msg-notFoundIndexPattern').length).toBe(1); }); - it('renders notFound msg', async () => { + test('renders notFound msg', async () => { const search = jest.fn(() => Promise.reject({ status: 404 })); const comp = await mountDoc(search, true); expect(findTestSubject(comp, 'doc-msg-notFound').length).toBe(1); }); - it('renders error msg', async () => { + test('renders error msg', async () => { const search = jest.fn(() => Promise.reject('whatever')); const comp = await mountDoc(search, true); expect(findTestSubject(comp, 'doc-msg-error').length).toBe(1); }); - it('renders elasticsearch hit ', async () => { + test('renders elasticsearch hit ', async () => { const hit = { hits: { total: 1, hits: [{ _id: 1, _source: { test: 1 } }] } }; const search = jest.fn(() => Promise.resolve(hit)); const comp = await mountDoc(search, true); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx index 1f2d2fc532b578..0e0e6ed110ca67 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx @@ -19,11 +19,9 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; -import { IndexPatterns } from 'ui/index_patterns'; -import { metadata } from 'ui/metadata'; -import { ElasticSearchHit } from 'ui/registry/doc_views_types'; -import { DocViewer } from '../doc_viewer'; +import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; +import { IndexPatterns, ElasticSearchHit, getServices } from '../kibana_services'; export interface ElasticSearchResult { hits: { @@ -117,7 +115,9 @@ export function Doc(props: DocProps) { values={{ indexName: props.index }} />{' '} { - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - $scope.esClient = es; - $scope.id = $route.current.params.id; - $scope.index = $route.current.params.index; - $scope.indexPatternId = $route.current.params.indexPattern; - $scope.indexPatternService = indexPatterns; - }, - template: html, - k7Breadcrumbs: ($route: any) => [ - ...getRootBreadcrumbs(), - { - text: `${$route.current.params.index}#${$route.current.params.id}`, - }, - ], - }); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts index d1a01dadb72be8..538fbed821f00b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts +++ b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts @@ -17,9 +17,8 @@ * under the License. */ import { useEffect, useState } from 'react'; -import { ElasticSearchHit } from 'ui/registry/doc_views_types'; +import { ElasticSearchHit, IndexPattern } from '../kibana_services'; import { DocProps } from './doc'; -import { IndexPattern } from '../../../../data/public/index_patterns'; export enum ElasticRequestState { Loading, diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/index.js b/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/index.js deleted file mode 100644 index e6d638d88bc279..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/index.js +++ /dev/null @@ -1,33 +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 { uiModules } from 'ui/modules'; -import { ToolBarPagerText } from './tool_bar_pager_text'; -import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; -import { wrapInI18nContext } from 'ui/i18n'; - -const app = uiModules.get('kibana'); - -app.directive('toolBarPagerText', function (reactDirective) { - return reactDirective(wrapInI18nContext(ToolBarPagerText)); -}); - -app.directive('toolBarPagerButtons', function (reactDirective) { - return reactDirective(wrapInI18nContext(ToolBarPagerButtons)); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx index 433dca65f428e9..12473b25802f23 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx @@ -21,10 +21,28 @@ import { mount, shallow } from 'enzyme'; import { DocViewer } from './doc_viewer'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { addDocView, emptyDocViews, DocViewRenderProps } from 'ui/registry/doc_views'; +import { + addDocView, + emptyDocViews, + DocViewRenderProps, + getDocViewsSorted as mockGetDocViewsSorted, +} from 'ui/registry/doc_views'; + +jest.mock('../kibana_services', () => { + return { + getServices: () => ({ + docViewsRegistry: { + getDocViewsSorted: (hit: any) => { + return mockGetDocViewsSorted(hit); + }, + }, + }), + }; +}); beforeEach(() => { emptyDocViews(); + jest.clearAllMocks(); }); test('Render with 3 different tabs', () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx index 1c7aba9bcd7b2d..aa737ebd8dcf18 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { EuiTabbedContent } from '@elastic/eui'; -import { getDocViewsSorted, DocViewRenderProps } from 'ui/registry/doc_views'; +import { getServices, DocViewRenderProps } from '../kibana_services'; import { DocViewerTab } from './doc_viewer_tab'; /** @@ -28,21 +28,24 @@ import { DocViewerTab } from './doc_viewer_tab'; * a `render` function. */ export function DocViewer(renderProps: DocViewRenderProps) { - const tabs = getDocViewsSorted(renderProps.hit).map(({ title, render, component }, idx) => { - return { - id: title, - name: title, - content: ( - - ), - }; - }); + const { docViewsRegistry } = getServices(); + const tabs = docViewsRegistry + .getDocViewsSorted(renderProps.hit) + .map(({ title, render, component }, idx) => { + return { + id: title, + name: title, + content: ( + + ), + }; + }); if (!tabs.length) { // There there's a minimum of 2 tabs active in Discover. diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts deleted file mode 100644 index fa6145c45f55fc..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts +++ /dev/null @@ -1,47 +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. - */ - -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { DocViewer } from './doc_viewer'; - -uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { - return reactDirective( - DocViewer, - [ - 'hit', - ['indexPattern', { watchDepth: 'reference' }], - ['filter', { watchDepth: 'reference' }], - ['columns', { watchDepth: 'collection' }], - ['onAddColumn', { watchDepth: 'reference' }], - ['onRemoveColumn', { watchDepth: 'reference' }], - ], - { - restrict: 'E', - scope: { - hit: '=', - indexPattern: '=', - filter: '=?', - columns: '=?', - onAddColumn: '=?', - onRemoveColumn: '=?', - }, - } - ); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx index 3bb59a8dc958c9..5fa2d24dfa04cf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { DocViewRenderTab } from './doc_viewer_render_tab'; -import { DocViewRenderProps } from 'ui/registry/doc_views'; +import { DocViewRenderProps } from '../kibana_services'; test('Mounting and unmounting DocViewerRenderTab', () => { const unmountFn = jest.fn(); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx index 185ff163dad2a5..750ef6b6061e13 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useRef, useEffect } from 'react'; -import { DocViewRenderFn, DocViewRenderProps } from 'ui/registry/doc_views'; +import { DocViewRenderFn, DocViewRenderProps } from '../kibana_services'; interface Props { render: DocViewRenderFn; diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx index 0b25421d8aff3c..3721ba5818d412 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; -import { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; +import { DocViewRenderProps, DocViewRenderFn } from '../kibana_services'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts deleted file mode 100644 index 8ce94f24128df4..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts +++ /dev/null @@ -1,22 +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 './doc_viewer_directive'; - -export * from './doc_viewer'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts new file mode 100644 index 00000000000000..cdb8a6ad3ad46a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts @@ -0,0 +1,19 @@ +/* + * 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 const SEARCH_EMBEDDABLE_TYPE = 'search'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts index 3138008f3e3a00..beeb6a7338f9d2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts @@ -20,3 +20,4 @@ export * from './types'; export * from './search_embeddable_factory'; export * from './search_embeddable'; +export { SEARCH_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index eaec11ff893ed2..732fb6d2e4e709 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -16,42 +16,44 @@ * specific language governing permissions and limitations * under the License. */ - -// @ts-ignore -import { getFilterGenerator } from 'ui/filter_manager'; -import angular from 'angular'; import _ from 'lodash'; -import { SearchSource } from 'ui/courier'; -import { - getRequestInspectorStats, - getResponseInspectorStats, -} from 'ui/courier/utils/courier_inspector_utils'; -import { IndexPattern } from 'ui/index_patterns'; -import { RequestAdapter } from 'ui/inspector/adapters'; -import { Adapters } from 'ui/inspector/types'; -import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; -import { Filter, FilterStateStore } from '@kbn/es-query'; -import chrome from 'ui/chrome'; +import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -import { TimeRange } from 'src/plugins/data/public'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import { setup as data } from '../../../../data/public/legacy'; -import { Query, onlyDisabledFiltersChanged, getTime } from '../../../../data/public'; +import { npStart } from 'ui/new_platform'; +import { + esFilters, + TimeRange, + onlyDisabledFiltersChanged, + getTime, +} from '../../../../../../plugins/data/public'; +import { Query } from '../../../../data/public'; import { APPLY_FILTER_TRIGGER, - Embeddable, Container, + Embeddable, } from '../../../../embeddable_api/public/np_ready/public'; -import * as columnActions from '../doc_table/actions/columns'; +import * as columnActions from '../angular/doc_table/actions/columns'; import { SavedSearch } from '../types'; import searchTemplate from './search_template.html'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; -import { SortOrder } from '../doc_table/components/table_header/helpers'; -import { getSortForSearchSource } from '../doc_table/lib/get_sort_for_search_source'; +import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; +import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_search_source'; +import { + Adapters, + angular, + getFilterGenerator, + getRequestInspectorStats, + getResponseInspectorStats, + getServices, + IndexPattern, + RequestAdapter, + SearchSource, +} from '../kibana_services'; +import { SEARCH_EMBEDDABLE_TYPE } from './constants'; -const config = chrome.getUiSettingsClient(); +const { data } = npStart.plugins; interface SearchScope extends ng.IScope { columns?: string[]; @@ -79,7 +81,7 @@ export interface FilterManager { values: string | string[], operation: string, index: number - ) => Filter[]; + ) => esFilters.Filter[]; } interface SearchEmbeddableConfig { @@ -92,8 +94,6 @@ interface SearchEmbeddableConfig { queryFilter: unknown; } -export const SEARCH_EMBEDDABLE_TYPE = 'search'; - export class SearchEmbeddable extends Embeddable implements ISearchEmbeddable { private readonly savedSearch: SavedSearch; @@ -111,7 +111,7 @@ export class SearchEmbeddable extends Embeddable private abortController?: AbortController; private prevTimeRange?: TimeRange; - private prevFilters?: Filter[]; + private prevFilters?: esFilters.Filter[]; private prevQuery?: Query; constructor( @@ -142,9 +142,9 @@ export class SearchEmbeddable extends Embeddable requests: new RequestAdapter(), }; this.initializeSearchScope(); - this.autoRefreshFetchSubscription = data.timefilter.timefilter - .getAutoRefreshFetch$() - .subscribe(this.fetch); + const { timefilter } = data.query.timefilter; + + this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.fetch); this.subscription = Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => { this.panelTitle = this.output.title || ''; @@ -254,7 +254,7 @@ export class SearchEmbeddable extends Embeddable let filters = this.filterGen.generate(field, value, operator, indexPattern.id); filters = filters.map(filter => ({ ...filter, - $state: { store: FilterStateStore.APP_STATE }, + $state: { store: esFilters.FilterStateStore.APP_STATE }, })); await this.executeTriggerActions(APPLY_FILTER_TRIGGER, { @@ -277,7 +277,7 @@ export class SearchEmbeddable extends Embeddable if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - searchSource.setField('size', config.get('discover:sampleSize')); + searchSource.setField('size', getServices().uiSettings.get('discover:sampleSize')); searchSource.setField( 'sort', getSortForSearchSource(this.searchScope.sort, this.searchScope.indexPattern) @@ -319,7 +319,7 @@ export class SearchEmbeddable extends Embeddable // If the fetch was aborted, no need to surface this in the UI if (error.name === 'AbortError') return; - toastNotifications.addError(error, { + getServices().toastNotifications.addError(error, { title: i18n.translate('kbn.embeddable.errorTitle', { defaultMessage: 'Error fetching data', }), diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index d7b51b39e2a16d..1939cc70606217 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -16,24 +16,21 @@ * specific language governing permissions and limitations * under the License. */ - -import '../doc_table'; -import { capabilities } from 'ui/capabilities'; -import { npStart, npSetup } from 'ui/new_platform'; -import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; -import { TimeRange } from 'src/plugins/data/public'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import '../angular/doc_table'; +import { getServices } from '../kibana_services'; import { EmbeddableFactory, ErrorEmbeddable, Container, } from '../../../../../../plugins/embeddable/public'; +import { TimeRange } from '../../../../../../plugins/data/public'; import { SavedSearchLoader } from '../types'; -import { SearchEmbeddable, SEARCH_EMBEDDABLE_TYPE } from './search_embeddable'; +import { SearchEmbeddable } from './search_embeddable'; import { SearchInput, SearchOutput } from './types'; +import { SEARCH_EMBEDDABLE_TYPE } from './constants'; export class SearchEmbeddableFactory extends EmbeddableFactory< SearchInput, @@ -55,7 +52,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.get().discover.save as boolean; + return getServices().capabilities.discover.save as boolean; } public canCreateNew() { @@ -73,16 +70,18 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string; timeRange: TimeRange }, parent?: Container ): Promise { - const $injector = await chrome.dangerouslyGetActiveInjector(); + const $injector = await getServices().getInjector(); const $compile = $injector.get('$compile'); const $rootScope = $injector.get('$rootScope'); const searchLoader = $injector.get('savedSearches'); - const editUrl = chrome.addBasePath(`/app/kibana${searchLoader.urlFor(savedObjectId)}`); + const editUrl = await getServices().addBasePath( + `/app/kibana${searchLoader.urlFor(savedObjectId)}` + ); const Private = $injector.get('Private'); - const queryFilter = Private(FilterBarQueryFilterProvider); + const queryFilter = Private(getServices().FilterBarQueryFilterProvider); try { const savedObject = await searchLoader.get(savedObjectId); return new SearchEmbeddable( @@ -92,7 +91,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< $compile, editUrl, queryFilter, - editable: capabilities.get().discover.save as boolean, + editable: getServices().capabilities.discover.save as boolean, indexPatterns: _.compact([savedObject.searchSource.getField('index')]), }, input, @@ -110,5 +109,5 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< } } -const factory = new SearchEmbeddableFactory(npStart.plugins.uiActions.executeTriggerActions); -npSetup.plugins.embeddable.registerEmbeddableFactory(factory.type, factory); +const factory = new SearchEmbeddableFactory(getServices().uiActions.executeTriggerActions); +getServices().embeddable.registerEmbeddableFactory(factory.type, factory); diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts index bc46cdbe82981b..5473ec0e7b8b4d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -17,18 +17,18 @@ * under the License. */ -import { StaticIndexPattern } from 'ui/index_patterns'; import { TimeRange } from 'src/plugins/data/public'; import { Query } from 'src/legacy/core_plugins/data/public'; -import { Filter } from '@kbn/es-query'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from 'src/plugins/embeddable/public'; +import { StaticIndexPattern } from '../kibana_services'; import { SavedSearch } from '../types'; -import { SortOrder } from '../doc_table/components/table_header/helpers'; +import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; +import { esFilters } from '../../../../../../plugins/data/public'; export interface SearchInput extends EmbeddableInput { timeRange: TimeRange; query?: Query; - filters?: Filter[]; + filters?: esFilters.Filter[]; hidePanelTitles?: boolean; columns?: string[]; sort?: SortOrder[]; diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts new file mode 100644 index 00000000000000..eb8c2aec915581 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts @@ -0,0 +1,41 @@ +/* + * 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 { + FeatureCatalogueRegistryProvider, + FeatureCatalogueCategory, +} from 'ui/registry/feature_catalogue'; + +export function registerFeature() { + FeatureCatalogueRegistryProvider.register(() => { + return { + id: 'discover', + title: i18n.translate('kbn.discover.discoverTitle', { + defaultMessage: 'Discover', + }), + description: i18n.translate('kbn.discover.discoverDescription', { + defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', + }), + icon: 'discoverApp', + path: '/app/kibana#/discover', + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/index.js b/src/legacy/core_plugins/kibana/public/discover/index.js deleted file mode 100644 index e5099363062750..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/index.js +++ /dev/null @@ -1,46 +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 './saved_searches/saved_searches'; -import { i18n } from '@kbn/i18n'; - -import './angular/directives'; -import 'ui/collapsible_sidebar'; -import './components/field_chooser/field_chooser'; -import './angular/discover'; -import './doc_table/components/table_row'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import './doc'; -import './context'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'discover', - title: i18n.translate('kbn.discover.discoverTitle', { - defaultMessage: 'Discover', - }), - description: i18n.translate('kbn.discover.discoverDescription', { - defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', - }), - icon: 'discoverApp', - path: '/app/kibana#/discover', - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts new file mode 100644 index 00000000000000..35e48598f07a80 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { PluginInitializer, PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; +import { DiscoverPlugin, DiscoverSetup, DiscoverStart } from './plugin'; + +// Core will be looking for this when loading our plugin in the new platform +export const plugin: PluginInitializer = ( + initializerContext: PluginInitializerContext +) => { + return new DiscoverPlugin(initializerContext); +}; + +const pluginInstance = plugin({} as PluginInitializerContext); +export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts new file mode 100644 index 00000000000000..b78d05e68acade --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -0,0 +1,122 @@ +/* + * 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 'ui/collapsible_sidebar'; +import 'ui/directives/listen'; +import 'ui/directives/storage'; +import 'ui/fixed_scroll'; +import 'ui/directives/css_truncate'; + +import { npStart } from 'ui/new_platform'; +import chromeLegacy from 'ui/chrome'; +import angular from 'angular'; // just used in embeddables and discover controller +import uiRoutes from 'ui/routes'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { SearchSource } from 'ui/courier'; +// @ts-ignore +import { StateProvider } from 'ui/state_management/state'; +// @ts-ignore +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { timefilter } from 'ui/timefilter'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +// @ts-ignore +import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; +import { wrapInI18nContext } from 'ui/i18n'; +// @ts-ignore +import { docTitle } from 'ui/doc_title'; +// @ts-ignore +import * as docViewsRegistry from 'ui/registry/doc_views'; + +const services = { + // new plattform + addBasePath: npStart.core.http.basePath.prepend, + capabilities: npStart.core.application.capabilities, + chrome: npStart.core.chrome, + docLinks: npStart.core.docLinks, + eui_utils: npStart.plugins.eui_utils, + inspector: npStart.plugins.inspector, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + toastNotifications: npStart.core.notifications.toasts, + uiSettings: npStart.core.uiSettings, + uiActions: npStart.plugins.uiActions, + embeddable: npStart.plugins.embeddable, + // legacy + docTitle, + docViewsRegistry, + FilterBarQueryFilterProvider, + getInjector: () => { + return chromeLegacy.dangerouslyGetActiveInjector(); + }, + SavedObjectRegistryProvider, + SavedObjectProvider, + SearchSource, + ShareContextMenuExtensionsRegistryProvider, + StateProvider, + timefilter, + uiModules, + uiRoutes, + wrapInI18nContext, +}; +export function getServices() { + return services; +} + +// EXPORT legacy static dependencies +export { angular }; +export { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; +// @ts-ignore +export { callAfterBindingsWorkaround } from 'ui/compat'; +// @ts-ignore +export { getFilterGenerator } from 'ui/filter_manager'; +export { + getRequestInspectorStats, + getResponseInspectorStats, +} from 'ui/courier/utils/courier_inspector_utils'; +// @ts-ignore +export { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier'; +// @ts-ignore +export { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; +// @ts-ignore +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +// @ts-ignore +export { RequestAdapter } from 'ui/inspector/adapters'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { FieldList } from 'ui/index_patterns'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +export { showShareContextMenu } from 'ui/share'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +// @ts-ignore +export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; +// @ts-ignore +export { tabifyAggResponse } from 'ui/agg_response/tabify'; +// @ts-ignore +export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; + +// EXPORT types +export { VisProvider } from 'ui/vis'; +export { StaticIndexPattern, IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns'; +export { SearchSource } from 'ui/courier'; +export { ElasticSearchHit } from 'ui/registry/doc_views_types'; +export { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; +export { Adapters } from 'ui/inspector/types'; diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts new file mode 100644 index 00000000000000..873c429bf705db --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; +import { IUiActionsStart } from 'src/plugins/ui_actions/public'; +import { registerFeature } from './helpers/register_feature'; +import './kibana_services'; +import { + Start as EmbeddableStart, + Setup as EmbeddableSetup, +} from '../../../../../plugins/embeddable/public'; + +/** + * These are the interfaces with your public contracts. You should export these + * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. + * @public + */ +export type DiscoverSetup = void; +export type DiscoverStart = void; +interface DiscoverSetupPlugins { + uiActions: IUiActionsStart; + embeddable: EmbeddableSetup; +} +interface DiscoverStartPlugins { + uiActions: IUiActionsStart; + embeddable: EmbeddableStart; +} + +export class DiscoverPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + setup(core: CoreSetup, plugins: DiscoverSetupPlugins): DiscoverSetup { + registerFeature(); + require('./angular'); + } + + start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart { + // TODO enable this when possible, seems it broke a functional test: + // dashboard mode Dashboard View Mode Dashboard viewer can paginate on a saved search + // const factory = new SearchEmbeddableFactory(plugins.uiActions.executeTriggerActions); + // plugins.embeddable.registerEmbeddableFactory(factory.type, factory); + } + + stop() {} +} diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js index 3903dc08454500..9bbc5baf4fc229 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js @@ -17,10 +17,10 @@ * under the License. */ -import 'ui/notify'; -import { uiModules } from 'ui/modules'; import { createLegacyClass } from 'ui/utils/legacy_class'; -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { getServices } from '../kibana_services'; + +const { uiModules, SavedObjectProvider } = getServices(); const module = uiModules.get('discover/saved_searches', []); @@ -40,7 +40,7 @@ module.factory('SavedSearch', function (Private) { columns: [], hits: 0, sort: [], - version: 1 + version: 1, }, }); @@ -55,7 +55,7 @@ module.factory('SavedSearch', function (Private) { hits: 'integer', columns: 'keyword', sort: 'keyword', - version: 'integer' + version: 'integer', }; // Order these fields to the top, the rest are alphabetical diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js index 8460ccf923cf3a..9554642c225fd5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js @@ -17,10 +17,10 @@ * under the License. */ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { getServices } from '../kibana_services'; import './saved_searches'; -SavedObjectRegistryProvider.register((savedSearches) => { +getServices().SavedObjectRegistryProvider.register((savedSearches) => { return savedSearches; }); diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js index f29ebe6a47141b..0c3b52fbf0640c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js @@ -19,11 +19,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import rison from 'rison-node'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - import { EuiButton, EuiFlexGroup, @@ -34,6 +32,7 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; +import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; const SEARCH_OBJECT_TYPE = 'search'; diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js index 2bec532110379e..3531088e3847c9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js @@ -20,6 +20,14 @@ import React from 'react'; import { shallow } from 'enzyme'; +jest.mock('../kibana_services', () => { + return { + getServices: () => ({ + SavedObjectFinder: jest.fn() + }), + }; +}); + import { OpenSearchPanel, } from './open_search_panel'; diff --git a/src/legacy/core_plugins/kibana/public/discover/types.d.ts b/src/legacy/core_plugins/kibana/public/discover/types.d.ts index f285e943698936..7d8740243ec02b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/discover/types.d.ts @@ -17,8 +17,8 @@ * under the License. */ -import { SearchSource } from 'ui/courier'; -import { SortOrder } from './doc_table/components/table_header/helpers'; +import { SearchSource } from './kibana_services'; +import { SortOrder } from './angular/doc_table/components/table_header/helpers'; export interface SavedSearch { readonly id: string; diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap index fcf60a6009b29c..71c336b1d48d20 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap @@ -2,6 +2,7 @@ exports[`home directories should not render directory entry when showOnHomePage is false 1`] = ` { + const basePath = getServices().getBasePath(); const renderCards = () => { const apmData = { title: intl.formatMessage({ diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js index d7c8e9daa99dae..07f415cfcb1c9d 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js @@ -20,15 +20,20 @@ import React from 'react'; import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; -jest.mock( - 'ui/chrome', - () => ({ +jest.mock('../kibana_services', () =>{ + const mock = { getBasePath: jest.fn(() => 'path'), - }), - { virtual: true } -); + }; + return { + getServices: () => mock, + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); test('render', () => { const component = shallowWithIntl( { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('mlEnabled', () => { @@ -47,7 +52,7 @@ test('mlEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('apmUiEnabled', () => { @@ -57,7 +62,7 @@ test('apmUiEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('isNewKibanaInstance', () => { @@ -67,5 +72,5 @@ test('isNewKibanaInstance', () => { isNewKibanaInstance={true} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 6a8dff2ad4fa72..7f67b7ea0f3e1b 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -22,7 +22,6 @@ import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { AddData } from './add_data'; import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; import { EuiButton, @@ -40,6 +39,7 @@ import { import { Welcome } from './welcome'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { getServices } from '../kibana_services'; const KEY_ENABLE_WELCOME = 'home:welcome:show'; @@ -47,7 +47,10 @@ export class Home extends Component { constructor(props) { super(props); - const isWelcomeEnabled = !(chrome.getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'); + const isWelcomeEnabled = !( + getServices().getInjected('disableWelcomeScreen') || + props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false' + ); this.state = { // If welcome is enabled, we wait for loading to complete @@ -133,7 +136,7 @@ export class Home extends Component { const { apmUiEnabled, mlEnabled } = this.props; return ( - + diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/components/home.test.js index aa520ba2ed5f92..c21c6fa3d98a56 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.js @@ -25,6 +25,13 @@ import { shallow } from 'enzyme'; import { Home } from './home'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +jest.mock('../kibana_services', () =>({ + getServices: () => ({ + getBasePath: () => 'path', + getInjected: () => '' + }) +})); + describe('home', () => { let defaultProps; diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts index 621c058c803db0..cd7bc82fe33459 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts @@ -17,7 +17,12 @@ * under the License. */ -import { notificationServiceMock, overlayServiceMock } from '../../../../../../core/public/mocks'; +import { + notificationServiceMock, + overlayServiceMock, + httpServiceMock, + injectedMetadataServiceMock, +} from '../../../../../../core/public/mocks'; jest.doMock('ui/new_platform', () => { return { @@ -29,22 +34,9 @@ jest.doMock('ui/new_platform', () => { npStart: { core: { overlays: overlayServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath: 'path' }), + injectedMetadata: injectedMetadataServiceMock.createStartContract(), }, }, }; }); - -jest.doMock( - 'ui/chrome', - () => ({ - getBasePath: jest.fn(() => 'path'), - getInjected: jest.fn(() => ''), - }), - { virtual: true } -); - -jest.doMock('ui/capabilities', () => ({ - catalogue: {}, - management: {}, - navLinks: {}, -})); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 9aa44863f6d70c..e4a6753e0771a2 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -18,41 +18,46 @@ */ import React from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import { Home } from './home'; import { FeatureDirectory } from './feature_directory'; import { TutorialDirectory } from './tutorial_directory'; import { Tutorial } from './tutorial/tutorial'; -import { - HashRouter as Router, - Switch, - Route -} from 'react-router-dom'; +import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; -import { telemetryOptInProvider, shouldShowTelemetryOptIn } from '../kibana_services'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; export function HomeApp({ directories }) { - const isCloudEnabled = chrome.getInjected('isCloudEnabled', false); - const apmUiEnabled = chrome.getInjected('apmUiEnabled', true); - const mlEnabled = chrome.getInjected('mlEnabled', false); - const savedObjectsClient = chrome.getSavedObjectsClient(); + const { + telemetryOptInProvider, + shouldShowTelemetryOptIn, + getInjected, + savedObjectsClient, + getBasePath, + addBasePath, + } = getServices(); - const renderTutorialDirectory = (props) => { + const isCloudEnabled = getInjected('isCloudEnabled', false); + const apmUiEnabled = getInjected('apmUiEnabled', true); + const mlEnabled = getInjected('mlEnabled', false); + const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); + + const renderTutorialDirectory = props => { return ( ); }; - const renderTutorial = (props) => { + const renderTutorial = props => { return ( - - - - - - - - - - - + + + + + + + + + + + + + + + + + ); } HomeApp.propTypes = { - directories: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, - showOnHomePage: PropTypes.bool.isRequired, - category: PropTypes.string.isRequired - })), + directories: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + showOnHomePage: PropTypes.bool.isRequired, + category: PropTypes.string.isRequired, + }) + ), }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js index 5556a70b7d6b83..53d922c4e0a26d 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js @@ -31,7 +31,7 @@ import { UNINSTALLED_STATUS, } from './sample_data_set_card'; -import { toastNotifications } from 'ui/notify'; +import { getServices } from '../kibana_services'; import { listSampleDataSets, @@ -40,15 +40,14 @@ import { } from '../sample_data_client'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; - -const IS_DARK_THEME = chrome.getUiSettingsClient().get('theme:darkMode'); export class SampleDataSetCards extends React.Component { constructor(props) { super(props); + this.toastNotifications = getServices().toastNotifications; + this.state = { sampleDataSets: [], processingStatus: {}, @@ -70,7 +69,7 @@ export class SampleDataSetCards extends React.Component { try { sampleDataSets = await listSampleDataSets(); } catch (fetchError) { - toastNotifications.addDanger({ + this.toastNotifications.addDanger({ title: i18n.translate('kbn.home.sampleDataSet.unableToLoadListErrorMessage', { defaultMessage: 'Unable to load sample data sets list' } ), @@ -109,7 +108,7 @@ export class SampleDataSetCards extends React.Component { processingStatus: { ...prevState.processingStatus, [id]: false } })); } - toastNotifications.addDanger({ + this.toastNotifications.addDanger({ title: i18n.translate('kbn.home.sampleDataSet.unableToInstallErrorMessage', { defaultMessage: 'Unable to install sample data set: {name}', values: { name: targetSampleDataSet.name } } ), @@ -130,7 +129,7 @@ export class SampleDataSetCards extends React.Component { })); } - toastNotifications.addSuccess({ + this.toastNotifications.addSuccess({ title: i18n.translate('kbn.home.sampleDataSet.installedLabel', { defaultMessage: '{name} installed', values: { name: targetSampleDataSet.name } } ), @@ -155,7 +154,7 @@ export class SampleDataSetCards extends React.Component { processingStatus: { ...prevState.processingStatus, [id]: false } })); } - toastNotifications.addDanger({ + this.toastNotifications.addDanger({ title: i18n.translate('kbn.home.sampleDataSet.unableToUninstallErrorMessage', { defaultMessage: 'Unable to uninstall sample data set: {name}', values: { name: targetSampleDataSet.name } } ), @@ -176,7 +175,7 @@ export class SampleDataSetCards extends React.Component { })); } - toastNotifications.addSuccess({ + this.toastNotifications.addSuccess({ title: i18n.translate('kbn.home.sampleDataSet.uninstalledLabel', { defaultMessage: '{name} uninstalled', values: { name: targetSampleDataSet.name } } ), @@ -185,7 +184,9 @@ export class SampleDataSetCards extends React.Component { } lightOrDarkImage = (sampleDataSet) => { - return IS_DARK_THEME && sampleDataSet.darkPreviewImagePath ? sampleDataSet.darkPreviewImagePath : sampleDataSet.previewImagePath; + return getServices().uiSettings.get('theme:darkMode') && sampleDataSet.darkPreviewImagePath + ? sampleDataSet.darkPreviewImagePath + : sampleDataSet.previewImagePath; } render() { diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js index 1df99e9b03d11c..89d4909b0c66f5 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js @@ -27,9 +27,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; export class SampleDataViewDataButton extends React.Component { + addBasePath = getServices().addBasePath; state = { isPopoverOpen: false @@ -56,7 +57,7 @@ export class SampleDataViewDataButton extends React.Component { datasetName: this.props.name, }, }); - const dashboardPath = chrome.addBasePath(`/app/kibana#/dashboard/${this.props.overviewDashboard}`); + const dashboardPath = this.addBasePath(`/app/kibana#/dashboard/${this.props.overviewDashboard}`); if (this.props.appLinks.length === 0) { return ( @@ -79,7 +80,7 @@ export class SampleDataViewDataButton extends React.Component { size="m" /> ), - href: chrome.addBasePath(path) + href: this.addBasePath(path) }; }); const panels = [ diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js index b0551341965fa1..cc515993ac0614 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js @@ -17,19 +17,18 @@ * under the License. */ -jest.mock('ui/chrome', () => { - return { - addBasePath: (path) => { - return `root${path}`; - }, - }; -}); import React from 'react'; import { shallow } from 'enzyme'; import { SampleDataViewDataButton } from './sample_data_view_data_button'; +jest.mock('../kibana_services', () =>({ + getServices: () =>({ + addBasePath: path => `root${path}` + }) +})); + test('should render simple button when appLinks is empty', () => { const component = shallow(({ + getServices: () =>({ + getBasePath: jest.fn(() => 'path'), + chrome: { + setBreadcrumbs: () => {} + } + }) +})); jest.mock('../../../../../kibana_react/public', () => { return { Markdown: () =>
, diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js index eae549f8a6ac0c..0c537c8e9ae8ac 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js @@ -22,7 +22,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { SampleDataSetCards } from './sample_data_set_cards'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; import { EuiPage, @@ -112,7 +112,7 @@ class TutorialDirectoryUi extends React.Component { async componentDidMount() { this._isMounted = true; - chrome.breadcrumbs.set([ + getServices().chrome.setBreadcrumbs([ { text: homeTitle, href: '#/home', diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx index 8869819290263c..afe43a23e18cb0 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -33,15 +33,11 @@ import { EuiIcon, EuiPortal, } from '@elastic/eui'; -// @ts-ignore -import { banners } from 'ui/notify'; - import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; + import { SampleDataCard } from './sample_data'; import { TelemetryOptInCard } from './telemetry_opt_in'; -// @ts-ignore -import { trackUiMetric, METRIC_TYPE } from '../kibana_services'; interface Props { urlBasePath: string; @@ -51,6 +47,7 @@ interface Props { getTelemetryBannerId: () => string; shouldShowTelemetryOptIn: boolean; } + interface State { step: number; } @@ -59,6 +56,7 @@ interface State { * Shows a full-screen welcome page that gives helpful quick links to beginners. */ export class Welcome extends React.PureComponent { + private services = getServices(); public readonly state: State = { step: 0, }; @@ -70,31 +68,35 @@ export class Welcome extends React.PureComponent { }; private redirecToSampleData() { - const path = chrome.addBasePath('#/home/tutorial_directory/sampleData'); + const path = this.services.addBasePath('#/home/tutorial_directory/sampleData'); window.location.href = path; } + private async handleTelemetrySelection(confirm: boolean) { const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`; - trackUiMetric(METRIC_TYPE.CLICK, metricName); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, metricName); await this.props.setOptIn(confirm); const bannerId = this.props.getTelemetryBannerId(); - banners.remove(bannerId); + this.services.banners.remove(bannerId); this.setState(() => ({ step: 1 })); } private onSampleDataDecline = () => { - trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline'); this.props.onSkip(); }; private onSampleDataConfirm = () => { - trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm'); this.redirecToSampleData(); }; componentDidMount() { - trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); + this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount'); if (this.props.shouldShowTelemetryOptIn) { - trackUiMetric(METRIC_TYPE.COUNT, 'welcomeScreenWithTelemetryOptIn'); + this.services.trackUiMetric( + this.services.METRIC_TYPE.COUNT, + 'welcomeScreenWithTelemetryOptIn' + ); } document.addEventListener('keydown', this.hideOnEsc); } diff --git a/src/legacy/core_plugins/kibana/public/home/home_ng_wrapper.html b/src/legacy/core_plugins/kibana/public/home/home_ng_wrapper.html deleted file mode 100644 index 645855766fab8e..00000000000000 --- a/src/legacy/core_plugins/kibana/public/home/home_ng_wrapper.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js deleted file mode 100644 index 8233df680edfdf..00000000000000 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ /dev/null @@ -1,60 +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 chrome from 'ui/chrome'; -import routes from 'ui/routes'; -import template from './home_ng_wrapper.html'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -import { - HomeApp -} from './components/home_app'; -import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; - -const app = uiModules.get('apps/home', []); -app.directive('homeApp', function (reactDirective) { - return reactDirective(wrapInI18nContext(HomeApp)); -}); - -const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); - -function getRoute() { - return { - template, - controller($scope, Private) { - $scope.directories = Private(FeatureCatalogueRegistryProvider).inTitleOrder; - $scope.recentlyAccessed = npStart.core.chrome.recentlyAccessed.get().map(item => { - item.link = chrome.addBasePath(item.link); - return item; - }); - }, - k7Breadcrumbs: () => [ - { text: homeTitle }, - ] - }; -} - -// All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't -// redirect us to the default page by encountering a url it isn't marked as being able to handle. -routes.when('/home', getRoute()); -routes.when('/home/feature_directory', getRoute()); -routes.when('/home/tutorial_directory/:tab?', getRoute()); -routes.when('/home/tutorial/:id', getRoute()); diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts new file mode 100644 index 00000000000000..4ebf719b86233a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -0,0 +1,80 @@ +/* + * 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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { npSetup, npStart } from 'ui/new_platform'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { start as data } from '../../../data/public/legacy'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { localApplicationService } from '../local_application_service'; + +export const trackUiMetric = createUiStatsReporter('Kibana_home'); + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await chrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + const telemetryOptInProvider = Private(TelemetryOptInProvider); + + return { + telemetryOptInProvider, + shouldShowTelemetryOptIn: + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(), + }; +} + +let copiedLegacyCatalogue = false; + +(async () => { + const instance = new HomePlugin(); + instance.setup(npSetup.core, { + __LEGACY: { + trackUiMetric, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + METRIC_TYPE, + getFeatureCatalogueEntries: async () => { + if (!copiedLegacyCatalogue) { + const injector = await chrome.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + // Merge legacy registry with new registry + (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( + npSetup.plugins.feature_catalogue.register + ); + copiedLegacyCatalogue = true; + } + return npStart.plugins.feature_catalogue.get(); + }, + getAngularDependencies, + localApplicationService, + }, + }); + instance.start(npStart.core, { + data, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.js b/src/legacy/core_plugins/kibana/public/home/kibana_services.js deleted file mode 100644 index 792c5e09435a49..00000000000000 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.js +++ /dev/null @@ -1,40 +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 { uiModules } from 'ui/modules'; -import { npStart } from 'ui/new_platform'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { TelemetryOptInProvider } from '../../../telemetry/public/services'; - -export let indexPatternService; -export let shouldShowTelemetryOptIn; -export let telemetryOptInProvider; - -export const trackUiMetric = createUiStatsReporter('Kibana_home'); -export { METRIC_TYPE }; - -uiModules.get('kibana').run(($injector) => { - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - const Private = $injector.get('Private'); - - telemetryOptInProvider = Private(TelemetryOptInProvider); - shouldShowTelemetryOptIn = telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); - indexPatternService = $injector.get('indexPatterns'); -}); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts new file mode 100644 index 00000000000000..6189204ee4cfc9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -0,0 +1,79 @@ +/* + * 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 { + ChromeStart, + DocLinksStart, + HttpStart, + LegacyNavLink, + NotificationsSetup, + OverlayStart, + SavedObjectsClientContract, + UiSettingsClientContract, + UiSettingsState, +} from 'kibana/public'; +import { UiStatsMetricType } from '@kbn/analytics'; +import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public'; + +export interface HomeKibanaServices { + indexPatternService: any; + getFeatureCatalogueEntries: () => Promise; + metadata: { + app: unknown; + bundleId: string; + nav: LegacyNavLink[]; + version: string; + branch: string; + buildNum: number; + buildSha: string; + basePath: string; + serverName: string; + devMode: boolean; + uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; + }; + getInjected: (name: string, defaultValue?: any) => unknown; + chrome: ChromeStart; + telemetryOptInProvider: any; + uiSettings: UiSettingsClientContract; + http: HttpStart; + savedObjectsClient: SavedObjectsClientContract; + toastNotifications: NotificationsSetup['toasts']; + banners: OverlayStart['banners']; + METRIC_TYPE: any; + trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; + getBasePath: () => string; + shouldShowTelemetryOptIn: boolean; + docLinks: DocLinksStart; + addBasePath: (url: string) => string; +} + +let services: HomeKibanaServices | null = null; + +export function setServices(newServices: HomeKibanaServices) { + services = newServices; +} + +export function getServices() { + if (!services) { + throw new Error( + 'Kibana services not set - are you trying to import this module from outside of the home app?' + ); + } + return services; +} diff --git a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js index d6b264154d4248..a6f19bc166dc74 100644 --- a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; -import chrome from 'ui/chrome'; +import { getServices } from './kibana_services'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -const baseUrl = chrome.addBasePath('/api/kibana/home/tutorials'); +const baseUrl = getServices().addBasePath('/api/kibana/home/tutorials'); const headers = new Headers(); headers.append('Accept', 'application/json'); headers.append('Content-Type', 'application/json'); @@ -47,7 +46,7 @@ async function loadTutorials() { tutorials = await response.json(); tutorialsLoaded = true; } catch(err) { - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.home.loadTutorials.unableToLoadErrorMessage', { defaultMessage: 'Unable to load tutorials' } ), diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts new file mode 100644 index 00000000000000..2a2ea371d7f3bd --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -0,0 +1,103 @@ +/* + * 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, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; +import { UiStatsMetricType } from '@kbn/analytics'; + +import { DataStart } from '../../../data/public'; +import { LocalApplicationService } from '../local_application_service'; +import { setServices } from './kibana_services'; +import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public'; + +export interface LegacyAngularInjectedDependencies { + telemetryOptInProvider: any; + shouldShowTelemetryOptIn: boolean; +} + +export interface HomePluginStartDependencies { + data: DataStart; +} + +export interface HomePluginSetupDependencies { + __LEGACY: { + trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; + METRIC_TYPE: any; + metadata: { + app: unknown; + bundleId: string; + nav: LegacyNavLink[]; + version: string; + branch: string; + buildNum: number; + buildSha: string; + basePath: string; + serverName: string; + devMode: boolean; + uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; + }; + getFeatureCatalogueEntries: () => Promise; + getAngularDependencies: () => Promise; + localApplicationService: LocalApplicationService; + }; +} + +export class HomePlugin implements Plugin { + private dataStart: DataStart | null = null; + private savedObjectsClient: any = null; + + setup( + core: CoreSetup, + { + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + }: HomePluginSetupDependencies + ) { + localApplicationService.register({ + id: 'home', + title: 'Home', + mount: async ({ core: contextCore }, params) => { + const angularDependencies = await getAngularDependencies(); + setServices({ + ...legacyServices, + http: contextCore.http, + toastNotifications: core.notifications.toasts, + banners: contextCore.overlays.banners, + getInjected: core.injectedMetadata.getInjectedVar, + docLinks: contextCore.docLinks, + savedObjectsClient: this.savedObjectsClient!, + chrome: contextCore.chrome, + uiSettings: core.uiSettings, + addBasePath: core.http.basePath.prepend, + getBasePath: core.http.basePath.get, + indexPatternService: this.dataStart!.indexPatterns.indexPatterns, + ...angularDependencies, + }); + const { renderApp } = await import('./render_app'); + return await renderApp(params.element); + }, + }); + } + + start(core: CoreStart, { data }: HomePluginStartDependencies) { + // TODO is this really the right way? I though the app context would give us those + this.dataStart = data; + this.savedObjectsClient = core.savedObjects.client; + } + + stop() {} +} diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/render_app.tsx new file mode 100644 index 00000000000000..a8c35144a45b01 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/render_app.tsx @@ -0,0 +1,38 @@ +/* + * 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 { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { HomeApp } from './components/home_app'; +import { getServices } from './kibana_services'; + +export const renderApp = async (element: HTMLElement) => { + const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); + const { getFeatureCatalogueEntries, chrome } = getServices(); + const directories = await getFeatureCatalogueEntries(); + chrome.setBreadcrumbs([{ text: homeTitle }]); + + render(, element); + + return () => { + unmountComponentAtNode(element); + }; +}; diff --git a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js index da46b3e16c0936..eca88604a559da 100644 --- a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js +++ b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js @@ -17,36 +17,36 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; -import chrome from 'ui/chrome'; -import { indexPatternService } from './kibana_services'; +import { getServices } from './kibana_services'; const sampleDataUrl = '/api/sample_data'; function clearIndexPatternsCache() { - indexPatternService.clearCache(); + getServices().indexPatternService.clearCache(); } export async function listSampleDataSets() { - return await kfetch({ method: 'GET', pathname: sampleDataUrl }); + return await getServices().http.get(sampleDataUrl); } export async function installSampleDataSet(id, sampleDataDefaultIndex) { - await kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` }); + await getServices().http.post(`${sampleDataUrl}/${id}`); - if (chrome.getUiSettingsClient().isDefault('defaultIndex')) { - chrome.getUiSettingsClient().set('defaultIndex', sampleDataDefaultIndex); + if (getServices().uiSettings.isDefault('defaultIndex')) { + getServices().uiSettings.set('defaultIndex', sampleDataDefaultIndex); } clearIndexPatternsCache(); } export async function uninstallSampleDataSet(id, sampleDataDefaultIndex) { - await kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` }); + await getServices().http.delete(`${sampleDataUrl}/${id}`); - if (!chrome.getUiSettingsClient().isDefault('defaultIndex') - && chrome.getUiSettingsClient().get('defaultIndex') === sampleDataDefaultIndex) { - chrome.getUiSettingsClient().set('defaultIndex', null); + const uiSettings = getServices().uiSettings; + + if (!uiSettings.isDefault('defaultIndex') + && uiSettings.get('defaultIndex') === sampleDataDefaultIndex) { + uiSettings.set('defaultIndex', null); } clearIndexPatternsCache(); diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png new file mode 100644 index 00000000000000..4b3020d91d57da Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/consul_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/consul_metrics/screenshot.png new file mode 100644 index 00000000000000..90aaa7477e8eb2 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/consul_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg new file mode 100644 index 00000000000000..72f0958f528245 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg @@ -0,0 +1,666 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/consul.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/consul.svg new file mode 100644 index 00000000000000..28bbadd24c8a65 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/consul.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 6c809e84c8c847..fe741a357cbfe7 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -58,6 +58,9 @@ import 'ui/agg_response'; import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; +import { localApplicationService } from './local_application_service'; + +localApplicationService.attachToAngular(routes); routes.enable(); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/index.ts b/src/legacy/core_plugins/kibana/public/local_application_service/index.ts new file mode 100644 index 00000000000000..2128355ca906ad --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/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 './local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts new file mode 100644 index 00000000000000..9d87e187fd1e1f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -0,0 +1,145 @@ +/* + * 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 { App, AppUnmount } from 'kibana/public'; +import { UIRoutes } from 'ui/routes'; +import { ILocationService, IScope } from 'angular'; +import { npStart } from 'ui/new_platform'; +import { htmlIdGenerator } from '@elastic/eui'; + +interface ForwardDefinition { + legacyAppId: string; + newAppId: string; + keepPrefix: boolean; +} + +const matchAllWithPrefix = (prefixOrApp: string | App) => + `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}/:tail*?`; + +/** + * To be able to migrate and shim parts of the Kibana app plugin + * while still running some parts of it in the legacy world, this + * service emulates the core application service while using the global + * angular router to switch between apps without page reload. + * + * The id of the apps is used as prefix of the route - when switching between + * to apps, the current application is unmounted. + * + * This service becomes unnecessary once the platform provides a central + * router that handles switching between applications without page reload. + */ +export class LocalApplicationService { + private apps: App[] = []; + private forwards: ForwardDefinition[] = []; + private idGenerator = htmlIdGenerator('kibanaAppLocalApp'); + + /** + * Register an app to be managed by the application service. + * This method works exactly as `core.application.register`. + * + * When an app is mounted, it is responsible for routing. The app + * won't be mounted again if the route changes within the prefix + * of the app (its id). It is fine to use whatever means for handling + * routing within the app. + * + * When switching to a URL outside of the current prefix, the app router + * shouldn't do anything because it doesn't own the routing anymore - + * the local application service takes over routing again, + * unmounts the current app and mounts the next app. + * + * @param app The app descriptor + */ + register(app: App) { + this.apps.push(app); + } + + /** + * Forwards every URL starting with `legacyAppId` to the same URL starting + * with `newAppId` - e.g. `/legacy/my/legacy/path?q=123` gets forwarded to + * `/newApp/my/legacy/path?q=123`. + * + * When setting the `keepPrefix` option, the new app id is simply prepended. + * The example above would become `/newApp/legacy/my/legacy/path?q=123`. + * + * This method can be used to provide backwards compatibility for URLs when + * renaming or nesting plugins. For route changes after the prefix, please + * use the routing mechanism of your app. + * + * @param legacyAppId The name of the old app to forward URLs from + * @param newAppId The name of the new app that handles the URLs now + * @param options Whether the prefix of the old app is kept to nest the legacy + * path into the new path + */ + forwardApp( + legacyAppId: string, + newAppId: string, + options: { keepPrefix: boolean } = { keepPrefix: false } + ) { + this.forwards.push({ legacyAppId, newAppId, ...options }); + } + + /** + * Wires up listeners to handle mounting and unmounting of apps to + * the legacy angular route manager. Once all apps within the Kibana + * plugin are using the local route manager, this implementation can + * be switched to a more lightweight implementation. + * + * @param angularRouteManager The current `ui/routes` instance + */ + attachToAngular(angularRouteManager: UIRoutes) { + this.apps.forEach(app => { + const wrapperElementId = this.idGenerator(); + angularRouteManager.when(matchAllWithPrefix(app), { + outerAngularWrapperRoute: true, + reloadOnSearch: false, + reloadOnUrl: false, + template: `
`, + controller($scope: IScope) { + const element = document.getElementById(wrapperElementId)!; + let unmountHandler: AppUnmount | null = null; + let isUnmounted = false; + $scope.$on('$destroy', () => { + if (unmountHandler) { + unmountHandler(); + } + isUnmounted = true; + }); + (async () => { + unmountHandler = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + // immediately unmount app if scope got destroyed in the meantime + if (isUnmounted) { + unmountHandler(); + } + })(); + }, + }); + }); + + this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { + angularRouteManager.when(matchAllWithPrefix(legacyAppId), { + resolveRedirectTo: ($location: ILocationService) => { + const url = $location.url(); + return `/${newAppId}${keepPrefix ? url : url.replace(legacyAppId, '')}`; + }, + }); + }); + } +} + +export const localApplicationService = new LocalApplicationService(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap index 08a8cf3898e948..8e7ae33a600682 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap @@ -463,7 +463,7 @@ exports[`Field for boolean setting should render as read only if saving is disab display="row" error={null} fullWidth={false} - hasChildLabel={true} + hasChildLabel={false} hasEmptyLabelSpace={false} helpText={null} isInvalid={false} @@ -557,7 +557,7 @@ exports[`Field for boolean setting should render as read only with help text if display="row" error={null} fullWidth={false} - hasChildLabel={true} + hasChildLabel={false} hasEmptyLabelSpace={false} helpText={ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js index e52f789eb8af78..a953d09906ed11 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js @@ -195,7 +195,7 @@ export class Field extends PureComponent { this.setState({ unsavedValue: newUnsavedValue, isInvalid, - error + error, }); }; @@ -764,6 +764,7 @@ export class Field extends PureComponent { helpText={this.renderHelpText(setting)} describedByIds={[`${setting.name}-aria`]} className="mgtAdvancedSettings__fieldRow" + hasChildLabel={setting.type !== 'boolean'} > {this.renderField(setting)} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html index 50e921e14973b6..0ef3cce832bc7b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html @@ -72,14 +72,6 @@ index-patterns="[indexPattern]" > - -
-
+

diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index dac0880e6fec44..f5011611368012 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -22,43 +22,48 @@ import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import '../saved_visualizations/saved_visualizations'; import './visualization_editor'; -import 'ui/vis/editors/default/sidebar'; -import 'ui/visualize'; -import 'ui/collapsible_sidebar'; +import './visualization'; -import { capabilities } from 'ui/capabilities'; -import chrome from 'ui/chrome'; import React from 'react'; -import angular from 'angular'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; -import { docTitle } from 'ui/doc_title'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; import { migrateAppState } from './lib'; -import uiRoutes from 'ui/routes'; -import { uiModules } from 'ui/modules'; import editorTemplate from './editor.html'; import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; -import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { timefilter } from 'ui/timefilter'; -import { getVisualizeLoader } from '../../../../../ui/public/visualize/loader'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; -import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs'; -import { npStart } from 'ui/new_platform'; -import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; -import { start as data } from '../../../../data/public/legacy'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; - +import { extractTimeFilter, changeTimeFilter } from '../../../../../../plugins/data/public'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; +import { + getServices, + angular, + absoluteToParsedUrl, + getUnhashableStatesProvider, + KibanaParsedUrl, + migrateLegacyQuery, + SavedObjectSaveModal, + showShareContextMenu, + showSaveModal, + stateMonitorFactory, + subscribeWithScope, +} from '../kibana_services'; + +const { + capabilities, + chrome, + chromeLegacy, + data, + docTitle, + FilterBarQueryFilterProvider, + getBasePath, + ShareContextMenuExtensionsRegistryProvider, + toastNotifications, + timefilter, + uiModules, + uiRoutes, + visualizations, +} = getServices(); + const { savedQueryService } = data.search.services; uiRoutes @@ -100,7 +105,7 @@ uiRoutes savedVis: function (savedVisualizations, redirectWhenMissing, $route) { return savedVisualizations.get($route.current.params.id) .then((savedVis) => { - npStart.core.chrome.recentlyAccessed.add( + chrome.recentlyAccessed.add( savedVis.getFullPath(), savedVis.title, savedVis.id); @@ -142,6 +147,7 @@ function VisEditor( AppState, $window, $injector, + $timeout, indexPatterns, kbnUrl, redirectWhenMissing, @@ -167,7 +173,7 @@ function VisEditor( dirty: !savedVis.id }; - $scope.topNavMenu = [...(capabilities.get().visualize.save ? [{ + $scope.topNavMenu = [...(capabilities.visualize.save ? [{ id: 'save', label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { @@ -236,7 +242,7 @@ function VisEditor( showShareContextMenu({ anchorElement, allowEmbed: true, - allowShortUrl: capabilities.get().visualize.createShortUrl, + allowShortUrl: capabilities.visualize.createShortUrl, getUnhashableStates, objectId: savedVis.id, objectType: 'visualization', @@ -353,9 +359,9 @@ function VisEditor( } }); - $scope.showSaveQuery = capabilities.get().visualize.saveQuery; + $scope.showSaveQuery = capabilities.visualize.saveQuery; - $scope.$watch(() => capabilities.get().visualize.saveQuery, (newCapability) => { + $scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => { $scope.showSaveQuery = newCapability; }); @@ -403,22 +409,21 @@ function VisEditor( $appStatus.dirty = status.dirty || !savedVis.id; }); - $scope.$watch('state.query', (newQuery) => { - const query = migrateLegacyQuery(newQuery); - $scope.updateQueryAndFetch({ query }); + $scope.$watch('state.query', (newQuery, oldQuery) => { + if (!_.isEqual(newQuery, oldQuery)) { + const query = migrateLegacyQuery(newQuery); + if (!_.isEqual(query, newQuery)) { + $state.query = query; + } + $scope.fetch(); + } }); $state.replace(); const updateTimeRange = () => { $scope.timeRange = timefilter.getTime(); - // In case we are running in embedded mode (i.e. we used the visualize loader to embed) - // the visualization, we need to update the timeRange on the visualize handler. - if ($scope._handler) { - $scope._handler.update({ - timeRange: $scope.timeRange, - }); - } + $scope.$broadcast('render'); }; const subscriptions = new Subscription(); @@ -435,9 +440,10 @@ function VisEditor( // update the searchSource when query updates $scope.fetch = function () { $state.save(); + $scope.query = $state.query; savedVis.searchSource.setField('query', $state.query); savedVis.searchSource.setField('filter', $state.filters); - $scope.vis.forceReload(); + $scope.$broadcast('render'); }; // update the searchSource when filters update @@ -460,16 +466,8 @@ function VisEditor( subscriptions.unsubscribe(); }); - if (!$scope.chrome.getVisible()) { - getVisualizeLoader().then(loader => { - $scope._handler = loader.embedVisualizationWithSavedObject($element.find('.visualize')[0], savedVis, { - timeRange: $scope.timeRange, - uiState: $scope.uiState, - appState: $state, - listenOnChange: false - }); - }); - } + + $timeout(() => { $scope.$broadcast('render'); }); } $scope.updateQueryAndFetch = function ({ query, dateRange }) { @@ -482,7 +480,9 @@ function VisEditor( timefilter.setTime(dateRange); // If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes - if (!isUpdate) $scope.fetch(); + if (!isUpdate) { + $scope.vis.forceReload(); + } }; $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { @@ -582,7 +582,7 @@ function VisEditor( if ($scope.isAddToDashMode()) { const savedVisualizationParsedUrl = new KibanaParsedUrl({ - basePath: chrome.getBasePath(), + basePath: getBasePath(), appId: kbnBaseUrl.slice('/app/'.length), appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }), }); @@ -591,15 +591,19 @@ function VisEditor( // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved // url, not the unsaved one. - chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); - const lastDashboardAbsoluteUrl = npStart.core.chrome.navLinks.get('kibana:dashboard').url; - const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, chrome.getBasePath()); + const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; + const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); kbnUrl.change(dashboardParsedUrl.appPath); } else if (savedVis.id === $route.current.params.id) { docTitle.change(savedVis.lastSavedTitle); - chrome.breadcrumbs.set($injector.invoke(getEditBreadcrumbs)); + chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); + savedVis.vis.title = savedVis.title; + savedVis.vis.description = savedVis.description; + // it's needed to save the state to update url string + $state.save(); } else { kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js new file mode 100644 index 00000000000000..198fbe19a0b7aa --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js @@ -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 { getServices } from '../kibana_services'; + +const { embeddables, uiModules } = getServices(); + +uiModules + .get('kibana/directive', ['ngSanitize']) + .directive('visualizationEmbedded', function (Private, $timeout, getAppState) { + + return { + restrict: 'E', + scope: { + savedObj: '=', + uiState: '=?', + timeRange: '=', + filters: '=', + query: '=', + }, + link: function ($scope, element) { + $scope.renderFunction = async () => { + if (!$scope._handler) { + $scope._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { + timeRange: $scope.timeRange, + filters: $scope.filters || [], + query: $scope.query, + appState: getAppState(), + uiState: $scope.uiState, + }); + $scope._handler.render(element[0]); + + } else { + $scope._handler.updateInput({ + timeRange: $scope.timeRange, + filters: $scope.filters || [], + query: $scope.query, + }); + } + }; + + $scope.$on('render', (event) => { + event.preventDefault(); + $timeout(() => { $scope.renderFunction(); }); + }); + + $scope.$on('$destroy', () => { + $scope._handler.destroy(); + }); + } + }; + }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index a2ed44df2f5b08..ead77e6bc41d50 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -17,10 +17,9 @@ * under the License. */ -import { debounce } from 'lodash'; -import { uiModules } from 'ui/modules'; -import 'angular-sanitize'; -import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; + +const { uiModules } = getServices(); uiModules .get('kibana/directive', ['ngSanitize']) @@ -34,6 +33,7 @@ uiModules uiState: '=?', timeRange: '=', filters: '=', + query: '=', }, link: function ($scope, element) { const editorType = $scope.savedObj.vis.type.editor; @@ -46,6 +46,7 @@ uiModules uiState: $scope.uiState, timeRange: $scope.timeRange, filters: $scope.filters, + query: $scope.query, appState: getAppState(), }); }; @@ -58,10 +59,6 @@ uiModules $scope.$on('$destroy', () => { editor.destroy(); }); - - $scope.$watchGroup(['timeRange', 'filters'], debounce(() => { - $scope.renderFunction(); - }, 100)); } }; }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx index 92bd0fa345fa0c..065feae0455972 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx @@ -19,7 +19,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Embeddable, EmbeddableOutput } from '../../../../../../plugins/embeddable/public'; + +import { Embeddable, EmbeddableOutput } from '../kibana_services'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 699fa68b4528bd..b6d5adcd98bd02 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,9 +17,14 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { StaticIndexPattern, getFromSavedObject } from 'ui/index_patterns'; -import { VisSavedObject } from 'ui/visualize/loader/types'; +import { + getServices, + getFromSavedObject, + StaticIndexPattern, + VisSavedObject, +} from '../kibana_services'; + +const { savedObjectsClient, uiSettings } = getServices(); export async function getIndexPattern( savedVis: VisSavedObject @@ -28,9 +33,7 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const config = chrome.getUiSettingsClient(); - const savedObjectsClient = chrome.getSavedObjectsClient(); - const defaultIndex = config.get('defaultIndex'); + const defaultIndex = uiSettings.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index b9febc3af54ea6..318686b26f6f27 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,27 +18,30 @@ */ import _ from 'lodash'; -import { StaticIndexPattern } from 'ui/index_patterns'; -import { PersistedState } from 'ui/persisted_state'; -import { VisualizeLoader } from 'ui/visualize/loader'; import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; -import { - VisSavedObject, - VisualizeLoaderParams, - VisualizeUpdateParams, -} from 'ui/visualize/loader/types'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; -import { Filter } from '@kbn/es-query'; -import { TimeRange } from '../../../../../../plugins/data/public'; import { + TimeRange, + onlyDisabledFiltersChanged, + esFilters, +} from '../../../../../../plugins/data/public'; +import { Query } from '../../../../data/public'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; + +import { + AppState, + Container, + Embeddable, EmbeddableInput, EmbeddableOutput, - Embeddable, - Container, -} from '../../../../../../plugins/embeddable/public'; -import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; -import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; + PersistedState, + StaticIndexPattern, + VisSavedObject, + VisualizeLoader, + VisualizeLoaderParams, + VisualizeUpdateParams, +} from '../kibana_services'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -48,15 +51,19 @@ export interface VisualizeEmbeddableConfiguration { editUrl: string; loader: VisualizeLoader; editable: boolean; + appState?: AppState; + uiState?: PersistedState; } export interface VisualizeInput extends EmbeddableInput { timeRange?: TimeRange; query?: Query; - filters?: Filter[]; + filters?: esFilters.Filter[]; vis?: { colors?: { [key: string]: string }; }; + appState?: AppState; + uiState?: PersistedState; } export interface VisualizeOutput extends EmbeddableOutput { @@ -69,12 +76,13 @@ export interface VisualizeOutput extends EmbeddableOutput { export class VisualizeEmbeddable extends Embeddable { private savedVisualization: VisSavedObject; private loader: VisualizeLoader; + private appState: AppState | undefined; private uiState: PersistedState; private handler?: EmbeddedVisualizeHandler; private timeRange?: TimeRange; private query?: Query; private title?: string; - private filters?: Filter[]; + private filters?: esFilters.Filter[]; private visCustomizations: VisualizeInput['vis']; private subscription: Subscription; public readonly type = VISUALIZE_EMBEDDABLE_TYPE; @@ -86,6 +94,8 @@ export class VisualizeEmbeddable extends Embeddable { this.handleChanges(); @@ -149,7 +165,7 @@ export class VisualizeEmbeddable extends Embeddable & { id: string }, parent?: Container ): Promise { - const $injector = await chrome.dangerouslyGetActiveInjector(); + const $injector = await getInjector(); const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); try { - const visId = savedObjectId; + const visId = savedObject.id as string; - const editUrl = chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`); + const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; const loader = await getVisualizeLoader(); - const savedObject = await savedVisualizations.get(visId); const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { @@ -160,6 +147,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< indexPatterns, editUrl, editable: this.isEditable(), + appState: input.appState, + uiState: input.uiState, }, input, parent @@ -170,6 +159,25 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } } + public async createFromSavedObject( + savedObjectId: string, + input: Partial & { id: string }, + parent?: Container + ): Promise { + const $injector = await getInjector(); + const savedVisualizations = $injector.get('savedVisualizations'); + + try { + const visId = savedObjectId; + + const savedObject = await savedVisualizations.get(visId); + return this.createFromObject(savedObject, input, parent); + } catch (e) { + console.error(e); // eslint-disable-line no-console + return new ErrorEmbeddable(e, input, parent); + } + } + public async create() { // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. @@ -183,8 +191,5 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { - npSetup.plugins.embeddable.registerEmbeddableFactory( - VISUALIZE_EMBEDDABLE_TYPE, - embeddableFactory - ); + embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js index d95f7ea85c5db2..40a1b79ea35203 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js @@ -20,7 +20,10 @@ import React, { Fragment, PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +import { getServices } from '../kibana_services'; + +const { docLinks } = getServices(); export class HelpMenu extends PureComponent { render() { @@ -31,7 +34,7 @@ export class HelpMenu extends PureComponent { diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js index aeabff2d97007b..58a92193de63e8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { + chrome.setHelpExtension(domElement => { render(, domElement); return () => { unmountComponentAtNode(domElement); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index b3c16fb94d7fbc..592a355a71b0d7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -21,16 +21,14 @@ import './editor/editor'; import { i18n } from '@kbn/i18n'; import './saved_visualizations/_saved_vis'; import './saved_visualizations/saved_visualizations'; -import uiRoutes from 'ui/routes'; -import 'ui/capabilities/route_setup'; import visualizeListingTemplate from './listing/visualize_listing.html'; import { VisualizeListingController } from './listing/visualize_listing'; import { VisualizeConstants } from './visualize_constants'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs'; -// load directives -import '../../../data/public'; +import { getServices, FeatureCatalogueCategory } from './kibana_services'; + +const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); uiRoutes .defaults(/visualize/, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts new file mode 100644 index 00000000000000..7e8435bbdc65ea --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -0,0 +1,132 @@ +/* + * 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 'angular-sanitize'; // used in visualization_editor.js and visualization.js +import 'ui/collapsible_sidebar'; // used in default editor +import 'ui/vis/editors/default/sidebar'; +// load directives +import '../../../data/public'; + +import { npStart } from 'ui/new_platform'; +import angular from 'angular'; // just used in editor.js +import chromeLegacy from 'ui/chrome'; + +import uiRoutes from 'ui/routes'; + +// @ts-ignore +import { docTitle } from 'ui/doc_title'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { wrapInI18nContext } from 'ui/i18n'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { timefilter } from 'ui/timefilter'; + +// Saved objects +import { SavedObjectsClientProvider } from 'ui/saved_objects'; +// @ts-ignore +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; + +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { start as data } from '../../../data/public/legacy'; +import { start as embeddables } from '../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; + +const services = { + // new platform + addBasePath: npStart.core.http.basePath.prepend, + capabilities: npStart.core.application.capabilities, + chrome: npStart.core.chrome, + docLinks: npStart.core.docLinks, + embeddable: npStart.plugins.embeddable, + getBasePath: npStart.core.http.basePath.get, + savedObjectsClient: npStart.core.savedObjects.client, + toastNotifications: npStart.core.notifications.toasts, + uiSettings: npStart.core.uiSettings, + + data, + embeddables, + visualizations, + + // legacy + chromeLegacy, + docTitle, + FeatureCatalogueRegistryProvider, + FilterBarQueryFilterProvider, + getInjector: () => { + return chromeLegacy.dangerouslyGetActiveInjector(); + }, + SavedObjectProvider, + SavedObjectRegistryProvider, + SavedObjectsClientProvider, + ShareContextMenuExtensionsRegistryProvider, + timefilter, + uiModules, + uiRoutes, + wrapInI18nContext, + + createUiStatsReporter, +}; + +export function getServices() { + return services; +} + +// export legacy static dependencies +export { angular }; +export { getFromSavedObject } from 'ui/index_patterns'; +export { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +// @ts-ignore +export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +export { showShareContextMenu } from 'ui/share'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; +export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { getVisualizeLoader } from 'ui/visualize/loader'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { + Container, + Embeddable, + EmbeddableFactory, + EmbeddableInput, + EmbeddableOutput, + ErrorEmbeddable, +} from '../../../../../plugins/embeddable/public'; + +// export types +export { METRIC_TYPE }; +export { StaticIndexPattern } from 'ui/index_patterns'; +export { AppState } from 'ui/state_management/app_state'; +export { VisType } from 'ui/vis'; +export { VisualizeLoader } from 'ui/visualize/loader'; +export { + VisSavedObject, + VisualizeLoaderParams, + VisualizeUpdateParams, +} from 'ui/visualize/loader/types'; + +// export const +export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js b/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js index 54a6048a22dafa..34b662838880e3 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js @@ -18,7 +18,8 @@ */ import React from 'react'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { KuiEmptyTablePrompt, @@ -27,7 +28,7 @@ import { KuiButtonIcon, } from '@kbn/ui-framework/components'; -function NoVisualizationsPromptUi({ onCreateVis, intl }) { +function NoVisualizationsPrompt({ onCreateVis }) { return ( } + icon={} > } - message={intl.formatMessage({ - id: 'kbn.visualize.listing.noVisualizationsText', + message={i18n.translate('kbn.visualize.listing.noVisualizationsText', { defaultMessage: `Looks like you don't have any visualizations. Let's create some!`, })} /> @@ -52,4 +52,4 @@ function NoVisualizationsPromptUi({ onCreateVis, intl }) { ); } -export const NoVisualizationsPrompt = injectI18n(NoVisualizationsPromptUi); +export { NoVisualizationsPrompt }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index bb05ce34413db1..f9e3a1a90115a3 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -17,21 +17,27 @@ * under the License. */ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import 'ui/directives/kbn_href'; -import { uiModules } from 'ui/modules'; -import { timefilter } from 'ui/timefilter'; -import chrome from 'ui/chrome'; -import { wrapInI18nContext } from 'ui/i18n'; -import { toastNotifications } from 'ui/notify'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { VisualizeListingTable } from './visualize_listing_table'; import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { i18n } from '@kbn/i18n'; +import { getServices } from '../kibana_services'; + +const { + addBasePath, + chrome, + chromeLegacy, + SavedObjectRegistryProvider, + SavedObjectsClientProvider, + timefilter, + toastNotifications, + uiModules, + wrapInI18nContext, + visualizations, +} = getServices(); + const app = uiModules.get('app/visualize', ['ngRoute', 'react']); app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable)) @@ -55,11 +61,11 @@ export function VisualizeListingController($injector, createNewVis) { this.editItem = ({ editUrl }) => { // for visualizations the edit and view URLs are the same - window.location = chrome.addBasePath(editUrl); + window.location.href = addBasePath(editUrl); }; this.getViewUrl = ({ editUrl }) => { - return chrome.addBasePath(editUrl); + return addBasePath(editUrl); }; this.closeNewVisModal = () => { @@ -101,7 +107,7 @@ export function VisualizeListingController($injector, createNewVis) { }) ) .then(() => { - chrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + chromeLegacy.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); }) .catch(error => { toastNotifications.addError(error, { @@ -112,7 +118,7 @@ export function VisualizeListingController($injector, createNewVis) { }); }; - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: i18n.translate('kbn.visualize.visualizeListingBreadcrumbsTitle', { defaultMessage: 'Visualize', diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index c909b6003516f4..fbd70a0d8c0f77 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -19,69 +19,53 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { capabilities } from 'ui/capabilities'; import { TableListView } from './../../table_list_view'; -import { - EuiIcon, - EuiBetaBadge, - EuiLink, - EuiButton, - EuiEmptyPrompt, -} from '@elastic/eui'; +import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -class VisualizeListingTableUi extends Component { +import { getServices } from '../kibana_services'; +const { capabilities } = getServices(); + +class VisualizeListingTable extends Component { constructor(props) { super(props); } render() { - const { intl } = this.props; return ( item.canDelete} initialFilter={''} noItemsFragment={this.getNoItemsMessage()} - entityName={ - intl.formatMessage({ - id: 'kbn.visualize.listing.table.entityName', - defaultMessage: 'visualization', - }) - } - entityNamePlural={ - intl.formatMessage({ - id: 'kbn.visualize.listing.table.entityNamePlural', - defaultMessage: 'visualizations', - }) - } - tableListTitle={ - intl.formatMessage({ - id: 'kbn.visualize.listing.table.listTitle', - defaultMessage: 'Visualizations', - }) - } + entityName={i18n.translate('kbn.visualize.listing.table.entityName', { + defaultMessage: 'visualization', + })} + entityNamePlural={i18n.translate('kbn.visualize.listing.table.entityNamePlural', { + defaultMessage: 'visualizations', + })} + tableListTitle={i18n.translate('kbn.visualize.listing.table.listTitle', { + defaultMessage: 'Visualizations', + })} /> ); } getTableColumns() { - const { intl } = this.props; const tableColumns = [ { field: 'title', - name: intl.formatMessage({ - id: 'kbn.visualize.listing.table.titleColumnName', + name: i18n.translate('kbn.visualize.listing.table.titleColumnName', { defaultMessage: 'Title', }), sortable: true, @@ -92,35 +76,29 @@ class VisualizeListingTableUi extends Component { > {field} - ) + ), }, { field: 'typeTitle', - name: intl.formatMessage({ - id: 'kbn.visualize.listing.table.typeColumnName', + name: i18n.translate('kbn.visualize.listing.table.typeColumnName', { defaultMessage: 'Type', }), sortable: true, - render: (field, record) => ( + render: (field, record) => ( {this.renderItemTypeIcon(record)} {record.typeTitle} {this.getBadge(record)} - ) + ), }, { field: 'description', - name: intl.formatMessage({ - id: 'kbn.dashboard.listing.table.descriptionColumnName', + name: i18n.translate('kbn.dashboard.listing.table.descriptionColumnName', { defaultMessage: 'Description', }), sortable: true, - render: (field, record) => ( - - {record.description} - - ) + render: (field, record) => {record.description}, }, ]; @@ -184,19 +162,13 @@ class VisualizeListingTableUi extends Component { />

); - } renderItemTypeIcon(item) { let icon; if (item.image) { icon = ( - + ); } else { icon = ( @@ -214,38 +186,40 @@ class VisualizeListingTableUi extends Component { getBadge(item) { if (item.stage === 'beta') { - return (); + return ( + + ); } else if (item.stage === 'experimental') { - return (); + return ( + + ); } } } -VisualizeListingTableUi.propTypes = { +VisualizeListingTable.propTypes = { deleteItems: PropTypes.func.isRequired, findItems: PropTypes.func.isRequired, createItem: PropTypes.func.isRequired, @@ -254,4 +228,4 @@ VisualizeListingTableUi.propTypes = { listingLimit: PropTypes.number.isRequired, }; -export const VisualizeListingTable = injectI18n(VisualizeListingTableUi); +export { VisualizeListingTable }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts index b321e5563eb60b..c83f7f5a5da8b0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisSavedObject } from 'ui/visualize/loader/types'; +import { VisSavedObject } from './kibana_services'; export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 341a839e9c776c..4aa614b68ea232 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -349,7 +349,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1` as="div" autoFocus={true} disabled={false} - lockProps={Object {}} + lockProps={ + Object { + "style": undefined, + } + } noFocusGuards={false} persistentFocus={false} returnFocus={true} @@ -1627,7 +1631,11 @@ exports[`NewVisModal should render as expected 1`] = ` as="div" autoFocus={true} disabled={false} - lockProps={Object {}} + lockProps={ + Object { + "style": undefined, + } + } noFocusGuards={false} persistentFocus={false} returnFocus={true} diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts deleted file mode 100644 index 04c99a1547ba93..00000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts +++ /dev/null @@ -1,26 +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. - */ - -export const settingsGet = jest.fn(); - -jest.doMock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get: settingsGet, - }), -})); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 6b2f51c3dcf2b1..99d9590e750fd0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -20,14 +20,33 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { settingsGet } from './new_vis_modal.test.mocks'; - import { NewVisModal } from './new_vis_modal'; - -import { VisType } from 'ui/vis'; +import { VisType } from '../kibana_services'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +jest.mock('../kibana_services', () => { + const mock = { + addBasePath: jest.fn(path => `root${path}`), + uiSettings: { get: jest.fn() }, + createUiStatsReporter: () => jest.fn(), + }; + + return { + getServices: () => mock, + VisType: {}, + METRIC_TYPE: 'metricType', + }; +}); + +import { getServices } from '../kibana_services'; + +beforeEach(() => { + jest.clearAllMocks(); +}); + describe('NewVisModal', () => { + const settingsGet = getServices().uiSettings.get as jest.Mock; + const defaultVisTypeParams = { hidden: false, visualization: class Controller { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 31ddafb4ec719a..420f0e5198056c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,14 +22,15 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { VisType } from 'ui/vis'; import { VisualizeConstants } from '../visualize_constants'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; +import { getServices, METRIC_TYPE, VisType } from '../kibana_services'; + +const { addBasePath, createUiStatsReporter, uiSettings } = getServices(); + interface TypeSelectionProps { isOpen: boolean; onClose: () => void; @@ -54,7 +55,7 @@ class NewVisModal extends React.Component void; visType: VisType; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx index 7fd1df85cee067..382f475669f5dc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx @@ -20,14 +20,21 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; -import chrome from 'ui/chrome'; -jest.doMock('ui/chrome'); +jest.mock('../../kibana_services', () => { + return { + getServices: () => ({ + addBasePath: jest.fn((url: string) => `testbasepath${url}`), + }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); describe('NewVisHelp', () => { it('should render as expected', () => { - (chrome.addBasePath as unknown) = (url: string) => `testbasepath${url}`; - expect( shallowWithIntl( { aliasUrl: '/my/fancy/new/thing', description: 'Some desc', highlighted: false, - icon: 'wahtever', + icon: 'whatever', name: 'whatever', promotion: { buttonText: 'Do it now!', diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx index 3877c01dcc6729..44c36f7d17d550 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx @@ -20,9 +20,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; -import chrome from 'ui/chrome'; import { VisTypeAliasListEntry } from './type_selection'; +import { getServices } from '../../kibana_services'; + interface Props { promotedTypes: VisTypeAliasListEntry[]; } @@ -42,7 +43,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

> = [ { @@ -102,7 +102,7 @@ const data: Array> = [ ]; test('collects dashboard and all dependencies', async () => { - const savedObjectClient = SavedObjectsClientMock.create(); + const savedObjectClient = savedObjectsClientMock.create(); savedObjectClient.bulkGet.mockImplementation(objects => { if (!objects) { throw new Error('Invalid test data'); diff --git a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js index 44fd6833c68f88..4ffe790bd51a18 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js @@ -28,16 +28,28 @@ export function registerValueSuggestions(server) { method: ['POST'], handler: async function (req) { const { index } = req.params; - const { field, query, boolFilter } = req.payload; + const { field: fieldName, query, boolFilter } = req.payload; const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + + const savedObjectsClient = req.getSavedObjectsClient(); + const savedObjectsResponse = await savedObjectsClient.find( + { type: 'index-pattern', fields: ['fields'], search: `"${index}"`, searchFields: ['title'] } + ); + const indexPattern = savedObjectsResponse.total > 0 ? savedObjectsResponse.saved_objects[0] : null; + const fields = indexPattern ? JSON.parse(indexPattern.attributes.fields) : null; + const field = fields ? fields.find((field) => field.name === fieldName) : fieldName; + const body = getBody( { field, query, boolFilter }, autocompleteTerminateAfter, autocompleteTimeout ); + try { const response = await callWithRequest(req, 'search', { index, body }); - const buckets = get(response, 'aggregations.suggestions.buckets') || []; + const buckets = get(response, 'aggregations.suggestions.buckets') + || get(response, 'aggregations.nestedSuggestions.suggestions.buckets') + || []; const suggestions = map(buckets, 'key'); return suggestions; } catch (error) { @@ -55,7 +67,7 @@ function getBody({ field, query, boolFilter = [] }, terminateAfter, timeout) { // the amount of information that needs to be transmitted to the coordinating node const shardSize = 10; - return { + const body = { size: 0, timeout: `${timeout}ms`, terminate_after: terminateAfter, @@ -67,7 +79,7 @@ function getBody({ field, query, boolFilter = [] }, terminateAfter, timeout) { aggs: { suggestions: { terms: { - field, + field: field.name || field, include: `${getEscapedQuery(query)}.*`, execution_hint: executionHint, shard_size: shardSize, @@ -75,6 +87,22 @@ function getBody({ field, query, boolFilter = [] }, terminateAfter, timeout) { }, }, }; + + if (field.subType && field.subType.nested) { + return { + ...body, + aggs: { + nestedSuggestions: { + nested: { + path: field.subType.nested.path, + }, + aggs: body.aggs, + }, + } + }; + } + + return body; } function getEscapedQuery(query = '') { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js new file mode 100644 index 00000000000000..e05be3bda145f7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js @@ -0,0 +1,63 @@ +/* + * 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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function cockroachdbMetricsSpecProvider(server, context) { + const moduleName = 'cockroachdb'; + return { + id: 'cockroachdbMetrics', + name: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.nameTitle', { + defaultMessage: 'CockroachDB metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from the CockroachDB server.', + }), + longDescription: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.longDescription', { + defaultMessage: 'The `cockroachdb` Metricbeat module fetches monitoring metrics from CockroachDB. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-cockroachdb.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/cockroachdb.svg', + artifacts: { + dashboards: [ + { + id: 'e3ba0c30-9766-11e9-9eea-6f554992ec1f', + linkLabel: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'CockroachDB metrics dashboard', + }), + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-cockroachdb.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/cockroachdb_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js new file mode 100644 index 00000000000000..8823fe5ee5067e --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js @@ -0,0 +1,63 @@ +/* + * 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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function consulMetricsSpecProvider(server, context) { + const moduleName = 'consul'; + return { + id: 'consulMetrics', + name: i18n.translate('kbn.server.tutorials.consulMetrics.nameTitle', { + defaultMessage: 'Consul metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.consulMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from the Consul server.', + }), + longDescription: i18n.translate('kbn.server.tutorials.consulMetrics.longDescription', { + defaultMessage: 'The `consul` Metricbeat module fetches monitoring metrics from Consul. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-consul.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/consul.svg', + artifacts: { + dashboards: [ + { + id: '496910f0-b952-11e9-a579-f5c0a5d81340', + linkLabel: i18n.translate('kbn.server.tutorials.consulMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Consul metrics dashboard', + }), + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-consul.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/consul_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js b/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js index 69f31c623e5efe..ca308ac969e493 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js @@ -46,7 +46,7 @@ export function corednsLogsSpecProvider(server, context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-coredns.html', }, }), - //TODO: euiIconType: 'logoCoredns', + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/coredns.svg', artifacts: { dashboards: [ { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index 0299d00856c641..a95baad4ccb657 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -77,6 +77,8 @@ import { ciscoLogsSpecProvider } from './cisco_logs'; import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; import { couchdbMetricsSpecProvider } from './couchdb_metrics'; import { emsBoundariesSpecProvider } from './ems'; +import { consulMetricsSpecProvider } from './consul_metrics'; +import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; export function registerTutorials(server) { server.registerTutorial(systemLogsSpecProvider); @@ -139,4 +141,6 @@ export function registerTutorials(server) { server.registerTutorial(envoyproxyLogsSpecProvider); server.registerTutorial(couchdbMetricsSpecProvider); server.registerTutorial(emsBoundariesSpecProvider); + server.registerTutorial(consulMetricsSpecProvider); + server.registerTutorial(cockroachdbMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/kibana_react/public/index.scss b/src/legacy/core_plugins/kibana_react/public/index.scss index 14922ca8ee8ebe..14b4687c459e11 100644 --- a/src/legacy/core_plugins/kibana_react/public/index.scss +++ b/src/legacy/core_plugins/kibana_react/public/index.scss @@ -1,5 +1,3 @@ @import 'src/legacy/ui/public/styles/styling_constants'; -@import './top_nav_menu/index'; - @import './markdown/index'; diff --git a/src/legacy/core_plugins/kibana_react/public/index.ts b/src/legacy/core_plugins/kibana_react/public/index.ts index 2497671ca98d26..7e68b6c3886ffc 100644 --- a/src/legacy/core_plugins/kibana_react/public/index.ts +++ b/src/legacy/core_plugins/kibana_react/public/index.ts @@ -23,6 +23,4 @@ // of the ExpressionExectorService /** @public types */ -export { TopNavMenu, TopNavMenuData } from './top_nav_menu'; - export { Markdown, MarkdownSimple } from './markdown'; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/index.ts b/src/legacy/core_plugins/kibana_react/public/top_nav_menu/index.ts deleted file mode 100644 index b45baaf0e47112..00000000000000 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/index.ts +++ /dev/null @@ -1,21 +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. - */ - -export { TopNavMenu } from './top_nav_menu'; -export { TopNavMenuData } from './top_nav_menu_data'; diff --git a/src/legacy/core_plugins/navigation/index.ts b/src/legacy/core_plugins/navigation/index.ts new file mode 100644 index 00000000000000..32d5f040760c66 --- /dev/null +++ b/src/legacy/core_plugins/navigation/index.ts @@ -0,0 +1,42 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from '../../../../kibana'; + +// eslint-disable-next-line import/no-default-export +export default function NavigationPlugin(kibana: any) { + const config: Legacy.PluginSpecOptions = { + id: 'navigation', + require: [], + publicDir: resolve(__dirname, 'public'), + config: (Joi: any) => { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + init: (server: Legacy.Server) => ({}), + uiExports: { + injectDefaultVars: () => ({}), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + }, + }; + + return new kibana.Plugin(config); +} diff --git a/src/legacy/core_plugins/navigation/package.json b/src/legacy/core_plugins/navigation/package.json new file mode 100644 index 00000000000000..8fddb8e6aeced3 --- /dev/null +++ b/src/legacy/core_plugins/navigation/package.json @@ -0,0 +1,4 @@ +{ + "name": "navigation", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/navigation/public/index.scss b/src/legacy/core_plugins/navigation/public/index.scss new file mode 100644 index 00000000000000..4c969b1a37e8c9 --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/index.scss @@ -0,0 +1,3 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './top_nav_menu/index'; diff --git a/src/legacy/core_plugins/navigation/public/index.ts b/src/legacy/core_plugins/navigation/public/index.ts new file mode 100644 index 00000000000000..439405cc90b57a --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/index.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// TODO these are imports from the old plugin world. +// Once the new platform is ready, they can get removed +// and handled by the platform itself in the setup method +// of the ExpressionExectorService + +/** @public types */ +export { TopNavMenu, TopNavMenuData } from './top_nav_menu'; +export { NavigationSetup, NavigationStart } from './plugin'; + +import { NavigationPlugin as Plugin } from './plugin'; +export function plugin() { + return new Plugin(); +} diff --git a/src/legacy/core_plugins/navigation/public/legacy.ts b/src/legacy/core_plugins/navigation/public/legacy.ts new file mode 100644 index 00000000000000..1783514e6fbdcd --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/legacy.ts @@ -0,0 +1,30 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +import { start as dataShim } from '../../data/public/legacy'; +import { plugin } from '.'; + +const navPlugin = plugin(); + +export const setup = navPlugin.setup(npSetup.core); + +export const start = navPlugin.start(npStart.core, { + data: dataShim, +}); diff --git a/src/legacy/core_plugins/navigation/public/plugin.ts b/src/legacy/core_plugins/navigation/public/plugin.ts new file mode 100644 index 00000000000000..65a0902dec9861 --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/plugin.ts @@ -0,0 +1,74 @@ +/* + * 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, CoreStart, Plugin } from 'kibana/public'; +import { TopNavMenuExtensionsRegistry, TopNavMenuExtensionsRegistrySetup } from './top_nav_menu'; +import { createTopNav } from './top_nav_menu/create_top_nav_menu'; +import { TopNavMenuProps } from './top_nav_menu/top_nav_menu'; +import { DataStart } from '../../data/public'; + +/** + * Interface for this plugin's returned `setup` contract. + * + * @public + */ +export interface NavigationSetup { + registerMenuItem: TopNavMenuExtensionsRegistrySetup['register']; +} + +/** + * Interface for this plugin's returned `start` contract. + * + * @public + */ +export interface NavigationStart { + ui: { + TopNavMenu: React.ComponentType; + }; +} + +export interface NavigationPluginStartDependencies { + data: DataStart; +} + +export class NavigationPlugin implements Plugin { + private readonly topNavMenuExtensionsRegistry: TopNavMenuExtensionsRegistry = new TopNavMenuExtensionsRegistry(); + + public setup(core: CoreSetup): NavigationSetup { + return { + registerMenuItem: this.topNavMenuExtensionsRegistry.register.bind( + this.topNavMenuExtensionsRegistry + ), + }; + } + + public start(core: CoreStart, { data }: NavigationPluginStartDependencies): NavigationStart { + const extensions = this.topNavMenuExtensionsRegistry.getAll(); + + return { + ui: { + TopNavMenu: createTopNav(data, extensions), + }, + }; + } + + public stop() { + this.topNavMenuExtensionsRegistry.clear(); + } +} diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/_index.scss b/src/legacy/core_plugins/navigation/public/top_nav_menu/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/_index.scss rename to src/legacy/core_plugins/navigation/public/top_nav_menu/_index.scss diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx new file mode 100644 index 00000000000000..fa0e5fcac74072 --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx @@ -0,0 +1,31 @@ +/* + * 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 { TopNavMenuProps, TopNavMenu } from './top_nav_menu'; +import { TopNavMenuData } from './top_nav_menu_data'; +import { DataStart } from '../../../../core_plugins/data/public'; + +export function createTopNav(data: DataStart, extraConfig: TopNavMenuData[]) { + return (props: TopNavMenuProps) => { + const config = (props.config || []).concat(extraConfig); + + return ; + }; +} diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts b/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts new file mode 100644 index 00000000000000..dd139bbd6410fd --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { TopNavMenu } from './top_nav_menu'; +export { TopNavMenuData } from './top_nav_menu_data'; +export * from './top_nav_menu_extensions_registry'; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx similarity index 80% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 21c5cef4ae9254..9077de89103277 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -22,26 +22,20 @@ import { TopNavMenu } from './top_nav_menu'; import { TopNavMenuData } from './top_nav_menu_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { timefilterServiceMock } from '../../../../core_plugins/data/public/timefilter/timefilter_service.mock'; -const timefilterSetupMock = timefilterServiceMock.createSetupContract(); - jest.mock('ui/new_platform'); -jest.mock('../../../../../../src/legacy/core_plugins/data/public/legacy', () => ({ - start: { - ui: { - SearchBar: () => {}, - }, +const mockTimeHistory = { + add: () => {}, + get: () => { + return []; }, - setup: {}, -})); +}; -jest.mock('../../../../core_plugins/data/public', () => { - return { - SearchBar: () =>
, - SearchBarProps: {}, - }; -}); +const dataShim = { + ui: { + SearchBar: () =>
, + }, +}; describe('TopNavMenu', () => { const TOP_NAV_ITEM_SELECTOR = 'TopNavMenuItem'; @@ -84,7 +78,12 @@ describe('TopNavMenu', () => { it('Should render search bar', () => { const component = shallowWithIntl( - + ); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.tsx similarity index 89% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index aec91c2aa6bc64..14599e76470c0a 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -24,13 +24,13 @@ import { I18nProvider } from '@kbn/i18n/react'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; -import { SearchBarProps } from '../../../../core_plugins/data/public'; -import { start as data } from '../../../data/public/legacy'; +import { SearchBarProps, DataStart } from '../../../../core_plugins/data/public'; -type Props = Partial & { +export type TopNavMenuProps = Partial & { appName: string; config?: TopNavMenuData[]; showSearchBar?: boolean; + data?: DataStart; }; /* @@ -42,8 +42,7 @@ type Props = Partial & { * **/ -export function TopNavMenu(props: Props) { - const { SearchBar } = data.ui; +export function TopNavMenu(props: TopNavMenuProps) { const { config, showSearchBar, ...searchBarProps } = props; function renderItems() { if (!config) return; @@ -58,7 +57,8 @@ export function TopNavMenu(props: Props) { function renderSearchBar() { // Validate presense of all required fields - if (!showSearchBar) return; + if (!showSearchBar || !props.data) return; + const { SearchBar } = props.data.ui; return ; } diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_data.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_data.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts new file mode 100644 index 00000000000000..c3eab3cce18e6b --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.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 { TopNavMenuData } from './top_nav_menu_data'; + +export class TopNavMenuExtensionsRegistry { + private menuItems: TopNavMenuData[]; + + constructor() { + this.menuItems = []; + } + + /** @public **/ + // Items registered into this registry will be appended to any TopNavMenu rendered in any application. + public register(menuItem: TopNavMenuData) { + this.menuItems.push(menuItem); + } + + /** @internal **/ + public getAll() { + return this.menuItems; + } + + /** @internal **/ + public clear() { + this.menuItems.length = 0; + } +} + +export type TopNavMenuExtensionsRegistrySetup = Pick; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.test.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.test.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js index eeed13edbe9f55..f64237000ae416 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js @@ -17,7 +17,6 @@ * under the License. */ -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; import { hashUrl } from 'ui/state_management/state_hashing'; import uiRoutes from 'ui/routes'; diff --git a/src/legacy/core_plugins/status_page/public/status_page.js b/src/legacy/core_plugins/status_page/public/status_page.js index b545a261c8739b..4467c31fca22c4 100644 --- a/src/legacy/core_plugins/status_page/public/status_page.js +++ b/src/legacy/core_plugins/status_page/public/status_page.js @@ -17,7 +17,6 @@ * under the License. */ -import 'ui/autoload/styles'; import 'ui/i18n'; import chrome from 'ui/chrome'; import { npStart } from 'ui/new_platform'; diff --git a/src/legacy/core_plugins/telemetry/index.ts b/src/legacy/core_plugins/telemetry/index.ts index 3271373449eb3a..4b6566415f3e18 100644 --- a/src/legacy/core_plugins/telemetry/index.ts +++ b/src/legacy/core_plugins/telemetry/index.ts @@ -48,6 +48,9 @@ const telemetry = (kibana: any) => { // `config` is used internally and not intended to be set config: Joi.string().default(Joi.ref('$defaultConfigPath')), banner: Joi.boolean().default(true), + lastVersionChecked: Joi.string() + .allow('') + .default(''), url: Joi.when('$dev', { is: true, then: Joi.string().default( @@ -77,7 +80,8 @@ const telemetry = (kibana: any) => { }, }, async replaceInjectedVars(originalInjectedVars: any, request: any) { - const telemetryOptedIn = await getTelemetryOptIn(request); + const currentKibanaVersion = getCurrentKibanaVersion(request.server); + const telemetryOptedIn = await getTelemetryOptIn({ request, currentKibanaVersion }); return { ...originalInjectedVars, @@ -97,7 +101,13 @@ const telemetry = (kibana: any) => { mappings, }, init(server: Server) { - const initializerContext = {} as PluginInitializerContext; + const initializerContext = { + env: { + packageInfo: { + version: getCurrentKibanaVersion(server), + }, + }, + } as PluginInitializerContext; const coreSetup = ({ http: { server }, @@ -116,3 +126,7 @@ const telemetry = (kibana: any) => { // eslint-disable-next-line import/no-default-export export default telemetry; + +function getCurrentKibanaVersion(server: Server): string { + return server.config().get('pkg.version'); +} diff --git a/src/legacy/core_plugins/telemetry/mappings.json b/src/legacy/core_plugins/telemetry/mappings.json index d83f7f59676306..1245ef88f58929 100644 --- a/src/legacy/core_plugins/telemetry/mappings.json +++ b/src/legacy/core_plugins/telemetry/mappings.json @@ -3,6 +3,9 @@ "properties": { "enabled": { "type": "boolean" + }, + "lastVersionChecked": { + "type": "keyword" } } } diff --git a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.test.ts b/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.test.ts new file mode 100644 index 00000000000000..67ad3aaae427d9 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.test.ts @@ -0,0 +1,214 @@ +/* + * 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 { getTelemetryOptIn } from './get_telemetry_opt_in'; + +describe('get_telemetry_opt_in', () => { + it('returns false when request path is not /app*', async () => { + const params = getCallGetTelemetryOptInParams({ + requestPath: '/foo/bar', + }); + + const result = await callGetTelemetryOptIn(params); + + expect(result).toBe(false); + }); + + it('returns null when saved object not found', async () => { + const params = getCallGetTelemetryOptInParams({ + savedObjectNotFound: true, + }); + + const result = await callGetTelemetryOptIn(params); + + expect(result).toBe(null); + }); + + it('returns false when saved object forbidden', async () => { + const params = getCallGetTelemetryOptInParams({ + savedObjectForbidden: true, + }); + + const result = await callGetTelemetryOptIn(params); + + expect(result).toBe(false); + }); + + it('throws an error on unexpected saved object error', async () => { + const params = getCallGetTelemetryOptInParams({ + savedObjectOtherError: true, + }); + + let threw = false; + try { + await callGetTelemetryOptIn(params); + } catch (err) { + threw = true; + expect(err.message).toBe(SavedObjectOtherErrorMessage); + } + + expect(threw).toBe(true); + }); + + it('returns null if enabled is null or undefined', async () => { + for (const enabled of [null, undefined]) { + const params = getCallGetTelemetryOptInParams({ + enabled, + }); + + const result = await callGetTelemetryOptIn(params); + + expect(result).toBe(null); + } + }); + + it('returns true when enabled is true', async () => { + const params = getCallGetTelemetryOptInParams({ + enabled: true, + }); + + const result = await callGetTelemetryOptIn(params); + + expect(result).toBe(true); + }); + + // build a table of tests with version checks, with results for enabled false + type VersionCheckTable = Array>; + + const EnabledFalseVersionChecks: VersionCheckTable = [ + { lastVersionChecked: '8.0.0', currentKibanaVersion: '8.0.0', result: false }, + { lastVersionChecked: '8.0.0', currentKibanaVersion: '8.0.1', result: false }, + { lastVersionChecked: '8.0.1', currentKibanaVersion: '8.0.0', result: false }, + { lastVersionChecked: '8.0.0', currentKibanaVersion: '8.1.0', result: null }, + { lastVersionChecked: '8.0.0', currentKibanaVersion: '9.0.0', result: null }, + { lastVersionChecked: '8.0.0', currentKibanaVersion: '7.0.0', result: false }, + { lastVersionChecked: '8.1.0', currentKibanaVersion: '8.0.0', result: false }, + { lastVersionChecked: '8.0.0-X', currentKibanaVersion: '8.0.0', result: false }, + { lastVersionChecked: '8.0.0', currentKibanaVersion: '8.0.0-X', result: false }, + { lastVersionChecked: null, currentKibanaVersion: '8.0.0', result: null }, + { lastVersionChecked: undefined, currentKibanaVersion: '8.0.0', result: null }, + { lastVersionChecked: 5, currentKibanaVersion: '8.0.0', result: null }, + { lastVersionChecked: '8.0.0', currentKibanaVersion: 'beta', result: null }, + { lastVersionChecked: 'beta', currentKibanaVersion: '8.0.0', result: null }, + { lastVersionChecked: 'beta', currentKibanaVersion: 'beta', result: false }, + { lastVersionChecked: 'BETA', currentKibanaVersion: 'beta', result: null }, + ].map(el => ({ ...el, enabled: false })); + + // build a table of tests with version checks, with results for enabled true/null/undefined + const EnabledTrueVersionChecks: VersionCheckTable = EnabledFalseVersionChecks.map(el => ({ + ...el, + enabled: true, + result: true, + })); + + const EnabledNullVersionChecks: VersionCheckTable = EnabledFalseVersionChecks.map(el => ({ + ...el, + enabled: null, + result: null, + })); + + const EnabledUndefinedVersionChecks: VersionCheckTable = EnabledFalseVersionChecks.map(el => ({ + ...el, + enabled: undefined, + result: null, + })); + + const AllVersionChecks = [ + ...EnabledFalseVersionChecks, + ...EnabledTrueVersionChecks, + ...EnabledNullVersionChecks, + ...EnabledUndefinedVersionChecks, + ]; + + test.each(AllVersionChecks)( + 'returns expected result for version check with %j', + async (params: Partial) => { + const result = await callGetTelemetryOptIn({ ...DefaultParams, ...params }); + expect(result).toBe(params.result); + } + ); +}); + +interface CallGetTelemetryOptInParams { + requestPath: string; + savedObjectNotFound: boolean; + savedObjectForbidden: boolean; + savedObjectOtherError: boolean; + enabled: boolean | null | undefined; + lastVersionChecked?: any; // should be a string, but test with non-strings + currentKibanaVersion: string; + result?: boolean | null; +} + +const DefaultParams = { + requestPath: '/app/something', + savedObjectNotFound: false, + savedObjectForbidden: false, + savedObjectOtherError: false, + enabled: true, + lastVersionChecked: '8.0.0', + currentKibanaVersion: '8.0.0', +}; + +function getCallGetTelemetryOptInParams( + overrides: Partial +): CallGetTelemetryOptInParams { + return { ...DefaultParams, ...overrides }; +} + +async function callGetTelemetryOptIn(params: CallGetTelemetryOptInParams): Promise { + const { currentKibanaVersion } = params; + const request = getMockRequest(params); + return await getTelemetryOptIn({ request, currentKibanaVersion }); +} + +function getMockRequest(params: CallGetTelemetryOptInParams): any { + return { + path: params.requestPath, + getSavedObjectsClient() { + return getMockSavedObjectsClient(params); + }, + }; +} + +const SavedObjectNotFoundMessage = 'savedObjectNotFound'; +const SavedObjectForbiddenMessage = 'savedObjectForbidden'; +const SavedObjectOtherErrorMessage = 'savedObjectOtherError'; + +function getMockSavedObjectsClient(params: CallGetTelemetryOptInParams) { + return { + async get(type: string, id: string) { + if (params.savedObjectNotFound) throw new Error(SavedObjectNotFoundMessage); + if (params.savedObjectForbidden) throw new Error(SavedObjectForbiddenMessage); + if (params.savedObjectOtherError) throw new Error(SavedObjectOtherErrorMessage); + + const enabled = params.enabled; + const lastVersionChecked = params.lastVersionChecked; + return { attributes: { enabled, lastVersionChecked } }; + }, + errors: { + isNotFoundError(error: any) { + return error.message === SavedObjectNotFoundMessage; + }, + isForbiddenError(error: any) { + return error.message === SavedObjectForbiddenMessage; + }, + }, + }; +} diff --git a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts index 9b365d6dd7ae51..c8bd4a4b6dfbd4 100644 --- a/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts +++ b/src/legacy/core_plugins/telemetry/server/get_telemetry_opt_in.ts @@ -17,7 +17,21 @@ * under the License. */ -export async function getTelemetryOptIn(request: any) { +import semver from 'semver'; + +import { SavedObjectAttributes } from './routes/opt_in'; + +interface GetTelemetryOptIn { + request: any; + currentKibanaVersion: string; +} + +// Returns whether telemetry has been opt'ed into or not. +// Returns null not set, meaning Kibana should prompt in the UI. +export async function getTelemetryOptIn({ + request, + currentKibanaVersion, +}: GetTelemetryOptIn): Promise { const isRequestingApplication = request.path.startsWith('/app'); // Prevent interstitial screens (such as the space selector) from prompting for telemetry @@ -27,9 +41,9 @@ export async function getTelemetryOptIn(request: any) { const savedObjectsClient = request.getSavedObjectsClient(); + let savedObject; try { - const { attributes } = await savedObjectsClient.get('telemetry', 'telemetry'); - return attributes.enabled; + savedObject = await savedObjectsClient.get('telemetry', 'telemetry'); } catch (error) { if (savedObjectsClient.errors.isNotFoundError(error)) { return null; @@ -43,4 +57,50 @@ export async function getTelemetryOptIn(request: any) { throw error; } + + const { attributes }: { attributes: SavedObjectAttributes } = savedObject; + + // if enabled is already null, return null + if (attributes.enabled == null) return null; + + const enabled = !!attributes.enabled; + + // if enabled is true, return it + if (enabled === true) return enabled; + + // Additional check if they've already opted out (enabled: false): + // - if the Kibana version has changed by at least a minor version, + // return null to re-prompt. + + const lastKibanaVersion = attributes.lastVersionChecked; + + // if the last kibana version isn't set, or is somehow not a string, return null + if (typeof lastKibanaVersion !== 'string') return null; + + // if version hasn't changed, just return enabled value + if (lastKibanaVersion === currentKibanaVersion) return enabled; + + const lastSemver = parseSemver(lastKibanaVersion); + const currentSemver = parseSemver(currentKibanaVersion); + + // if either version is invalid, return null + if (lastSemver == null || currentSemver == null) return null; + + // actual major/minor version comparison, for cases when to return null + if (currentSemver.major > lastSemver.major) return null; + if (currentSemver.major === lastSemver.major) { + if (currentSemver.minor > lastSemver.minor) return null; + } + + // current version X.Y is not greater than last version X.Y, return enabled + return enabled; +} + +function parseSemver(version: string): semver.SemVer | null { + // semver functions both return nulls AND throw exceptions: "it depends!" + try { + return semver.parse(version); + } catch (err) { + return null; + } } diff --git a/src/legacy/core_plugins/telemetry/server/index.ts b/src/legacy/core_plugins/telemetry/server/index.ts index b8ae5fc231fba6..aa13fab9a5f819 100644 --- a/src/legacy/core_plugins/telemetry/server/index.ts +++ b/src/legacy/core_plugins/telemetry/server/index.ts @@ -25,5 +25,5 @@ export { getTelemetryOptIn } from './get_telemetry_opt_in'; export { telemetryCollectionManager } from './collection_manager'; export const telemetryPlugin = (initializerContext: PluginInitializerContext) => - new TelemetryPlugin(); + new TelemetryPlugin(initializerContext); export { constants }; diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts index 70de51b2abe995..a5f0f1234799a4 100644 --- a/src/legacy/core_plugins/telemetry/server/plugin.ts +++ b/src/legacy/core_plugins/telemetry/server/plugin.ts @@ -17,14 +17,21 @@ * under the License. */ -import { CoreSetup } from 'src/core/server'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { registerRoutes } from './routes'; import { telemetryCollectionManager } from './collection_manager'; import { getStats } from './telemetry_collection'; export class TelemetryPlugin { + private readonly currentKibanaVersion: string; + + constructor(initializerContext: PluginInitializerContext) { + this.currentKibanaVersion = initializerContext.env.packageInfo.version; + } + public setup(core: CoreSetup) { + const currentKibanaVersion = this.currentKibanaVersion; telemetryCollectionManager.setStatsGetter(getStats, 'local'); - registerRoutes(core); + registerRoutes({ core, currentKibanaVersion }); } } diff --git a/src/legacy/core_plugins/telemetry/server/routes/index.ts b/src/legacy/core_plugins/telemetry/server/routes/index.ts index 12ba541d699f9f..2eb6bf95b4f45e 100644 --- a/src/legacy/core_plugins/telemetry/server/routes/index.ts +++ b/src/legacy/core_plugins/telemetry/server/routes/index.ts @@ -21,7 +21,12 @@ import { CoreSetup } from 'src/core/server'; import { registerOptInRoutes } from './opt_in'; import { registerTelemetryDataRoutes } from './telemetry_stats'; -export function registerRoutes(core: CoreSetup) { - registerOptInRoutes(core); +interface RegisterRoutesParams { + core: CoreSetup; + currentKibanaVersion: string; +} + +export function registerRoutes({ core, currentKibanaVersion }: RegisterRoutesParams) { + registerOptInRoutes({ core, currentKibanaVersion }); registerTelemetryDataRoutes(core); } diff --git a/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts b/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts index aabc0259f08fc2..3a7194890b5700 100644 --- a/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts +++ b/src/legacy/core_plugins/telemetry/server/routes/opt_in.ts @@ -21,7 +21,17 @@ import Joi from 'joi'; import { boomify } from 'boom'; import { CoreSetup } from 'src/core/server'; -export function registerOptInRoutes(core: CoreSetup) { +interface RegisterOptInRoutesParams { + core: CoreSetup; + currentKibanaVersion: string; +} + +export interface SavedObjectAttributes { + enabled?: boolean; + lastVersionChecked: string; +} + +export function registerOptInRoutes({ core, currentKibanaVersion }: RegisterOptInRoutesParams) { const { server } = core.http as any; server.route({ @@ -36,17 +46,16 @@ export function registerOptInRoutes(core: CoreSetup) { }, handler: async (req: any, h: any) => { const savedObjectsClient = req.getSavedObjectsClient(); + const savedObject: SavedObjectAttributes = { + enabled: req.payload.enabled, + lastVersionChecked: currentKibanaVersion, + }; + const options = { + id: 'telemetry', + overwrite: true, + }; try { - await savedObjectsClient.create( - 'telemetry', - { - enabled: req.payload.enabled, - }, - { - id: 'telemetry', - overwrite: true, - } - ); + await savedObjectsClient.create('telemetry', savedObject, options); } catch (err) { return boomify(err); } diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js index 5b8bb075775108..ca798b6bf2470e 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js @@ -21,9 +21,8 @@ import { get } from 'lodash'; import { GeohashLayer } from './geohash_layer'; import { BaseMapsVisualizationProvider } from './base_maps_visualization'; import { TileMapTooltipFormatterProvider } from './editors/_tooltip_formatter'; +import { npStart } from 'ui/new_platform'; import { getFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; -import { start as data } from '../../../core_plugins/data/public/legacy'; -const filterManager = data.filter.filterManager; export const createTileMapVisualization = ({ serviceSettings, $injector }) => { const BaseMapsVisualization = new BaseMapsVisualizationProvider(serviceSettings); @@ -189,6 +188,7 @@ export const createTileMapVisualization = ({ serviceSettings, $injector }) => { filter[filterName] = { ignore_unmapped: true }; filter[filterName][field] = filterData; + const { filterManager } = npStart.plugins.data.query; filterManager.addFilters([filter]); this.vis.updateState(); diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 77cd94279c879a..d989a68d40eeb4 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -21,13 +21,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; -import { setup as data } from '../../data/public/legacy'; import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; const setupPlugins: Readonly = { visualizations, - data, + data: npSetup.plugins.data, expressions: npSetup.plugins.expressions, // Temporary solution diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 6447e3bbc5f516..6291948f750775 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -25,12 +25,11 @@ import { HttpSetup, } from 'kibana/public'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; +import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; -import { DataSetup } from '../../data/public'; -import { TimefilterSetup } from '../../data/public/timefilter'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; @@ -39,14 +38,14 @@ export interface TimelionVisualizationDependencies extends LegacyDependenciesPlu uiSettings: UiSettingsClientContract; http: HttpSetup; timelionPanels: Map; - timefilter: TimefilterSetup; + timefilter: TimefilterContract; } /** @internal */ export interface TimelionPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; - data: DataSetup; + data: DataPublicPluginSetup; // Temporary solution __LEGACY: LegacyDependenciesPlugin; @@ -69,8 +68,8 @@ export class TimelionPlugin implements Plugin, void> { const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, http: core.http, - timefilter: data.timefilter, timelionPanels, + timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), }; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index eb4fb3f397149f..6239e4027c392a 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -18,12 +18,12 @@ */ // @ts-ignore -import { buildEsQuery, getEsQueryConfig, Filter } from '@kbn/es-query'; +import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; // @ts-ignore import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; import { Query } from 'src/legacy/core_plugins/data/public'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange, esFilters } from 'src/plugins/data/public'; import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; import { TimelionVisualizationDependencies } from '../plugin'; @@ -60,7 +60,7 @@ export function getTimelionRequestHandler(dependencies: TimelionVisualizationDep visParams, }: { timeRange: TimeRange; - filters: Filter[]; + filters: esFilters.Filter[]; query: Query; visParams: VisParams; forceFetch?: boolean; @@ -78,7 +78,7 @@ export function getTimelionRequestHandler(dependencies: TimelionVisualizationDep const esQueryConfigs = getEsQueryConfig(uiSettings); // parse the time range client side to make sure it behaves like other charts - const timeRangeBounds = timefilter.timefilter.calculateBounds(timeRange); + const timeRangeBounds = timefilter.calculateBounds(timeRange); try { return await http.post('../api/timelion/run', { diff --git a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts index 63adccb3e02b04..7310ee5b5f1720 100644 --- a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts +++ b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts @@ -47,14 +47,20 @@ interface AnalyicsReporterConfig { } export function createAnalyticsReporter(config: AnalyicsReporterConfig) { - const { localStorage, basePath, $http, debug } = config; + const { localStorage, basePath, debug } = config; return createReporter({ debug, storage: localStorage, async http(report) { const url = `${basePath}/api/telemetry/report`; - await $http.post(url, { report }); + await fetch(url, { + method: 'POST', + headers: { + 'kbn-xsrf': 'true', + }, + body: JSON.stringify({ report }), + }); }, }); } diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 89f74ef4b53e99..3b2e503a01e38a 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -171,7 +171,7 @@ export const createMetricVisFn = (): ExpressionFunction< throw new Error('colorRange must be provided when using percentage'); } - const fontSize = Number.parseInt(args.font.spec.fontSize, 10); + const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10); return { type: 'render', diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js index 3497a35f5c99d3..842d3aa6c4ad75 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js @@ -32,10 +32,10 @@ import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../common/extract_index_patterns'; import { npStart } from 'ui/new_platform'; -import { Storage } from 'ui/storage'; + import { CoreStartContextProvider } from '../contexts/query_input_bar_context'; import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -const localStorage = new Storage(window.localStorage); +import { Storage } from '../../../../../plugins/kibana_utils/public'; import { timefilter } from 'ui/timefilter'; const VIS_STATE_DEBOUNCE_DELAY = 200; @@ -46,6 +46,7 @@ export class VisEditor extends Component { super(props); const { vis } = props; this.appState = vis.API.getAppState(); + this.localStorage = new Storage(window.localStorage); this.state = { model: props.visParams, dirty: false, @@ -63,7 +64,7 @@ export class VisEditor extends Component { appName: APP_NAME, uiSettings: npStart.core.uiSettings, savedObjectsClient: npStart.core.savedObjects.client, - store: localStorage, + store: this.localStorage, }; } @@ -169,7 +170,7 @@ export class VisEditor extends Component { { + this._subscription = this._handler.handler.data$.subscribe(data => { this.setPanelInterval(data.visData); onDataChange(data); }); @@ -152,10 +150,12 @@ class VisEditorVisualizationUI extends Component { this._loadVisualization(); } - componentDidUpdate(prevProps) { - if (this._handler && !isEqual(this.props.timeRange, prevProps.timeRange)) { - this._handler.update({ + componentDidUpdate() { + if (this._handler) { + this._handler.updateInput({ timeRange: this.props.timeRange, + filters: this.props.filters || [], + query: this.props.query, }); } } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.test.js deleted file mode 100644 index e60626f8fbf0a8..00000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.test.js +++ /dev/null @@ -1,71 +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. - */ - -jest.mock('ui/visualize/loader/visualize_loader', () => ({})); - -jest.mock('ui/new_platform'); - -import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { VisEditorVisualization } from './vis_editor_visualization'; - -describe('getVisualizeLoader', () => { - let updateStub; - - beforeEach(() => { - updateStub = jest.fn(); - const handlerMock = { - update: updateStub, - data$: { - subscribe: () => {}, - }, - }; - const loaderMock = { - embedVisualizationWithSavedObject: () => handlerMock, - }; - require('ui/visualize/loader/visualize_loader').getVisualizeLoader = async () => loaderMock; - }); - - it('should not call _handler.update until getVisualizeLoader returns _handler', async () => { - const wrapper = mountWithIntl(); - - // Set prop to force DOM change and componentDidUpdate to be triggered - wrapper.setProps({ - timeRange: { - from: '2019-03-20T20:35:37.637Z', - to: '2019-03-23T18:40:16.486Z', - }, - }); - - expect(updateStub).not.toHaveBeenCalled(); - - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - - // Set prop to force DOM change and componentDidUpdate to be triggered - wrapper.setProps({ - timeRange: { - from: 'now/d', - to: 'now/d', - }, - }); - - expect(updateStub).toHaveBeenCalled(); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts b/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts index 19519571f1ab02..925b483905d01c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts @@ -18,14 +18,14 @@ */ import React from 'react'; -import { Storage } from 'ui/storage'; import { UiSettingsClientContract, SavedObjectsClientContract } from 'src/core/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; export interface ICoreStartContext { appName: string; uiSettings: UiSettingsClientContract; savedObjectsClient: SavedObjectsClientContract; - store: Storage; + storage: IStorageWrapper; } export const CoreStartContext = React.createContext(null); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_index_pattern_service.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_index_pattern_service.js index 0766e6a48765f9..54e90ab7dd9b7f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_index_pattern_service.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_index_pattern_service.js @@ -17,7 +17,7 @@ * under the License. */ -import { IndexPatternsService } from '../../../../server/index_patterns/service'; +import { IndexPatternsFetcher } from '../../../../../plugins/data/server/'; export const getIndexPatternService = { assign: 'indexPatternsService', method(req) { @@ -25,6 +25,6 @@ export const getIndexPatternService = { const callDataCluster = (...args) => { return dataCluster.callWithRequest(req, ...args); }; - return new IndexPatternsService(callDataCluster); + return new IndexPatternsFetcher(callDataCluster); }, }; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts index 22a71bd999d54e..b4c32f37eb90c1 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter } from '@kbn/es-query'; + import { timefilter } from 'ui/timefilter'; import { TimeRange } from 'src/plugins/data/public'; import { Query } from 'src/legacy/core_plugins/data/public'; - -// @ts-ignore import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; + +import { esFilters } from '../../../../plugins/data/public'; + // @ts-ignore import { VegaParser } from './data_model/vega_parser'; // @ts-ignore @@ -35,7 +36,7 @@ import { VisParams } from './vega_fn'; interface VegaRequestHandlerParams { query: Query; - filters: Filter; + filters: esFilters.Filter; timeRange: TimeRange; visParams: VisParams; } diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js index ec898080c581e8..5abb99e9bf716a 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -27,7 +27,7 @@ import { Utils } from '../data_model/utils'; import { VISUALIZATION_COLORS } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TooltipHandler } from './vega_tooltip'; -import { buildQueryFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../plugins/data/public'; import { getEnableExternalUrls } from '../helpers/vega_config_provider'; @@ -263,7 +263,7 @@ export class VegaBaseView { */ async addFilterHandler(query, index) { const indexId = await this._findIndex(index); - const filter = buildQueryFilter(query, indexId); + const filter = esFilters.buildQueryFilter(query, indexId); this._queryfilter.addFilters(filter); } @@ -274,7 +274,7 @@ export class VegaBaseView { async removeFilterHandler(query, index) { const $injector = await chrome.dangerouslyGetActiveInjector(); const indexId = await this._findIndex(index); - const filter = buildQueryFilter(query, indexId); + const filter = esFilters.buildQueryFilter(query, indexId); // This is a workaround for the https://github.com/elastic/kibana/issues/18863 // Once fixed, replace with a direct call (no await is needed because its not async) diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js index 1dc73d6f9ff20c..7aa60bb0cc4696 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js @@ -23,6 +23,7 @@ import { VegaView } from './vega_view/vega_view'; import { VegaMapView } from './vega_view/vega_map_view'; import { timefilter } from 'ui/timefilter'; import { start as data } from '../../../core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { findIndexPatternByTitle } from '../../data/public/index_patterns'; @@ -99,11 +100,12 @@ export const createVegaVisualization = ({ serviceSettings }) => class VegaVisual this._vegaView = null; } + const { filterManager } = npStart.plugins.data.query; const vegaViewParams = { parentEl: this._el, vegaParser, serviceSettings, - queryfilter: data.filter.filterManager, + queryfilter: filterManager, timefilter: timefilter, findIndex: this.findIndex.bind(this), }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 5dc0e452eea56c..5d7ab12a677cfd 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -42,6 +42,7 @@ const createSetupContract = (): VisualizationsSetup => ({ types: { registerVisualization: jest.fn(), registerAlias: jest.fn(), + hideTypes: jest.fn(), }, }); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts index 1556c3716de1ae..e0d26e34050489 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts @@ -48,16 +48,30 @@ export interface VisType { */ export class TypesService { private types: Record = {}; + private unregisteredHiddenTypes: string[] = []; public setup() { return { registerVisualization: (registerFn: () => VisType) => { const visDefinition = registerFn(); + if (this.unregisteredHiddenTypes.includes(visDefinition.name)) { + visDefinition.hidden = true; + } + if (this.types[visDefinition.name]) { throw new Error('type already exists!'); } this.types[visDefinition.name] = visDefinition; }, registerAlias: visTypeAliasRegistry.add, + hideTypes: (typeNames: string[]) => { + typeNames.forEach((name: string) => { + if (this.types[name]) { + this.types[name].hidden = true; + } else { + this.unregisteredHiddenTypes.push(name); + } + }); + }, }; } diff --git a/src/legacy/server/http/integration_tests/default_route_provider.test.ts b/src/legacy/server/http/integration_tests/default_route_provider.test.ts index d74be851a35355..4898cb2b67852e 100644 --- a/src/legacy/server/http/integration_tests/default_route_provider.test.ts +++ b/src/legacy/server/http/integration_tests/default_route_provider.test.ts @@ -44,7 +44,7 @@ describe('default route provider', () => { } throw Error(`unsupported ui setting: ${key}`); }, - getDefaults: () => { + getRegistered: () => { return { defaultRoute: { value: '/app/kibana', diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts index f627cf30a3cffd..0e7bcf1f56f6fc 100644 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ b/src/legacy/server/http/setup_default_route_provider.ts @@ -40,7 +40,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) { `Ignoring configured default route of '${defaultRoute}', as it is malformed.` ); - const fallbackRoute = uiSettings.getDefaults().defaultRoute.value; + const fallbackRoute = uiSettings.getRegistered().defaultRoute.value; const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`; return qualifiedFallbackRoute; diff --git a/src/legacy/server/index_patterns/index.ts b/src/legacy/server/index_patterns/index.ts index eea8a4a998e2dc..75d0038cf9023e 100644 --- a/src/legacy/server/index_patterns/index.ts +++ b/src/legacy/server/index_patterns/index.ts @@ -17,7 +17,9 @@ * under the License. */ -// @ts-ignore no types export { indexPatternsMixin } from './mixin'; -export { IndexPatternsService, FieldDescriptor } from './service'; +export { + IndexPatternsFetcher, + FieldDescriptor, +} from '../../../plugins/data/server/index_patterns/fetcher'; export { IndexPatternsServiceFactory } from './mixin'; diff --git a/src/legacy/server/index_patterns/mixin.ts b/src/legacy/server/index_patterns/mixin.ts index a7180d6a2d70ec..6b04c3842007bd 100644 --- a/src/legacy/server/index_patterns/mixin.ts +++ b/src/legacy/server/index_patterns/mixin.ts @@ -17,11 +17,10 @@ * under the License. */ -import { IndexPatternsService } from './service'; +import { IndexPatternsFetcher } from '../../../plugins/data/server'; import KbnServer from '../kbn_server'; import { APICaller, CallAPIOptions } from '../../../core/server'; import { Legacy } from '../../../../kibana'; -import { registerRoutes } from './routes'; export function indexPatternsMixin(kbnServer: KbnServer, server: Legacy.Server) { /** @@ -31,7 +30,7 @@ export function indexPatternsMixin(kbnServer: KbnServer, server: Legacy.Server) * @type {IndexPatternsService} */ server.decorate('server', 'indexPatternsServiceFactory', ({ callCluster }) => { - return new IndexPatternsService(callCluster); + return new IndexPatternsFetcher(callCluster); }); /** @@ -50,10 +49,8 @@ export function indexPatternsMixin(kbnServer: KbnServer, server: Legacy.Server) ) => callWithRequest(request, endpoint, params, options); return server.indexPatternsServiceFactory({ callCluster }); }); - - registerRoutes(kbnServer.newPlatform.setup.core); } export type IndexPatternsServiceFactory = (args: { callCluster: (endpoint: string, clientParams: any, options: any) => Promise; -}) => IndexPatternsService; +}) => IndexPatternsFetcher; diff --git a/src/legacy/server/index_patterns/routes.ts b/src/legacy/server/index_patterns/routes.ts deleted file mode 100644 index fb78b94e0f77f4..00000000000000 --- a/src/legacy/server/index_patterns/routes.ts +++ /dev/null @@ -1,138 +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 { first } from 'rxjs/operators'; -import { schema } from '@kbn/config-schema'; -import { - CoreSetup, - KibanaRequest, - RequestHandlerContext, - APICaller, - CallAPIOptions, -} from '../../../core/server'; -import { IndexPatternsService } from './service'; - -export function registerRoutes(core: CoreSetup) { - const getIndexPatternsService = async (request: KibanaRequest): Promise => { - const client = await core.elasticsearch.dataClient$.pipe(first()).toPromise(); - const callCluster: APICaller = ( - endpoint: string, - params?: Record, - options?: CallAPIOptions - ) => client.asScoped(request).callAsCurrentUser(endpoint, params, options); - return new Promise(resolve => resolve(new IndexPatternsService(callCluster))); - }; - - const parseMetaFields = (metaFields: string | string[]) => { - let parsedFields: string[] = []; - if (typeof metaFields === 'string') { - parsedFields = JSON.parse(metaFields); - } else { - parsedFields = metaFields; - } - return parsedFields; - }; - - const router = core.http.createRouter(); - router.get( - { - path: '/api/index_patterns/_fields_for_wildcard', - validate: { - query: schema.object({ - pattern: schema.string(), - meta_fields: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { - defaultValue: [], - }), - }), - }, - }, - async (context: RequestHandlerContext, request: any, response: any) => { - const indexPatterns = await getIndexPatternsService(request); - const { pattern, meta_fields: metaFields } = request.query; - - let parsedFields: string[] = []; - try { - parsedFields = parseMetaFields(metaFields); - } catch (error) { - return response.badRequest(); - } - - try { - const fields = await indexPatterns.getFieldsForWildcard({ - pattern, - metaFields: parsedFields, - }); - - return response.ok({ - body: { fields }, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - return response.notFound(); - } - } - ); - - router.get( - { - path: '/api/index_patterns/_fields_for_time_pattern', - validate: { - query: schema.object({ - pattern: schema.string(), - interval: schema.maybe(schema.string()), - look_back: schema.number({ min: 1 }), - meta_fields: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { - defaultValue: [], - }), - }), - }, - }, - async (context: RequestHandlerContext, request: any, response: any) => { - const indexPatterns = await getIndexPatternsService(request); - const { pattern, interval, look_back: lookBack, meta_fields: metaFields } = request.query; - - let parsedFields: string[] = []; - try { - parsedFields = parseMetaFields(metaFields); - } catch (error) { - return response.badRequest(); - } - - try { - const fields = await indexPatterns.getFieldsForTimePattern({ - pattern, - interval: interval ? interval : '', - lookBack, - metaFields: parsedFields, - }); - - return response.ok({ - body: { fields }, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - return response.notFound(); - } - } - ); -} diff --git a/src/legacy/server/index_patterns/service/index.ts b/src/legacy/server/index_patterns/service/index.ts deleted file mode 100644 index 496c4ba7b5b6f1..00000000000000 --- a/src/legacy/server/index_patterns/service/index.ts +++ /dev/null @@ -1,20 +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. - */ - -export * from './index_patterns_service'; diff --git a/src/legacy/server/index_patterns/service/index_patterns_service.ts b/src/legacy/server/index_patterns/service/index_patterns_service.ts deleted file mode 100644 index 6c32d99ad83f97..00000000000000 --- a/src/legacy/server/index_patterns/service/index_patterns_service.ts +++ /dev/null @@ -1,83 +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 { APICaller } from 'src/core/server'; - -import { getFieldCapabilities, resolveTimePattern, createNoMatchingIndicesError } from './lib'; - -export interface FieldDescriptor { - aggregatable: boolean; - name: string; - readFromDocValues: boolean; - searchable: boolean; - type: string; - esTypes: string[]; - parent?: string; - subType?: string; -} - -export class IndexPatternsService { - private _callDataCluster: APICaller; - - constructor(callDataCluster: APICaller) { - this._callDataCluster = callDataCluster; - } - - /** - * Get a list of field objects for an index pattern that may contain wildcards - * - * @param {Object} [options] - * @property {String} options.pattern The index pattern - * @property {Number} options.metaFields The list of underscore prefixed fields that should - * be left in the field list (all others are removed). - * @return {Promise>} - */ - async getFieldsForWildcard(options: { - pattern: string | string[]; - metaFields?: string[]; - }): Promise { - const { pattern, metaFields } = options; - return await getFieldCapabilities(this._callDataCluster, pattern, metaFields); - } - - /** - * Get a list of field objects for a time pattern - * - * @param {Object} [options={}] - * @property {String} options.pattern The moment compatible time pattern - * @property {Number} options.lookBack The number of indices we will pull mappings for - * @property {Number} options.metaFields The list of underscore prefixed fields that should - * be left in the field list (all others are removed). - * @return {Promise>} - */ - async getFieldsForTimePattern(options: { - pattern: string; - metaFields: string[]; - lookBack: number; - interval: string; - }) { - const { pattern, lookBack, metaFields } = options; - const { matches } = await resolveTimePattern(this._callDataCluster, pattern); - const indices = matches.slice(0, lookBack); - if (indices.length === 0) { - throw createNoMatchingIndicesError(pattern); - } - return await getFieldCapabilities(this._callDataCluster, indices, metaFields); - } -} diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/index.ts b/src/legacy/server/index_patterns/service/lib/field_capabilities/index.ts deleted file mode 100644 index 8ac42255577841..00000000000000 --- a/src/legacy/server/index_patterns/service/lib/field_capabilities/index.ts +++ /dev/null @@ -1,21 +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. - */ - -export { getFieldCapabilities } from './field_capabilities'; -export { FieldCapsResponse } from './field_caps_response'; diff --git a/src/legacy/server/index_patterns/service/lib/index.ts b/src/legacy/server/index_patterns/service/lib/index.ts deleted file mode 100644 index cd239b3753aa31..00000000000000 --- a/src/legacy/server/index_patterns/service/lib/index.ts +++ /dev/null @@ -1,24 +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. - */ - -export { getFieldCapabilities } from './field_capabilities'; -// @ts-ignore -export { resolveTimePattern } from './resolve_time_pattern'; -// @ts-ignore -export { createNoMatchingIndicesError } from './errors'; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index e7f2f4c85435ff..9cc4e30d4252dd 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -150,5 +150,5 @@ export default class KbnServer { export { Server, Request, ResponseToolkit } from 'hapi'; // Re-export commonly accessed api types. -export { IndexPatternsService } from './index_patterns'; +export { IndexPatternsFetcher as IndexPatternsService } from './index_patterns'; export { SavedObjectsLegacyService, SavedObjectsClient } from 'src/core/server'; diff --git a/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js b/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js index 87a1f1c9ece478..da44cc618eedd3 100644 --- a/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js @@ -269,7 +269,7 @@ export const getSavedObjects = () => [ "attributes": { "title": "kibana_sample_data_ecommerce", "timeFieldName": "order_date", - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"category\",\"subType\":\"multi\"},{\"name\":\"currency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_birth_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_first_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_first_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_first_name\",\"subType\":\"multi\"},{\"name\":\"customer_full_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_full_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_full_name\",\"subType\":\"multi\"},{\"name\":\"customer_gender\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_last_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_last_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_last_name\",\"subType\":\"multi\"},{\"name\":\"customer_phone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week_i\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"manufacturer\",\"subType\":\"multi\"},{\"name\":\"order_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"order_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products._id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products._id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products._id\",\"subType\":\"multi\"},{\"name\":\"products.base_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.base_unit_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products.category\",\"subType\":\"multi\"},{\"name\":\"products.created_on\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_percentage\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products.manufacturer\",\"subType\":\"multi\"},{\"name\":\"products.min_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.product_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products.product_name\",\"subType\":\"multi\"},{\"name\":\"products.quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.tax_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxful_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxless_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.unit_discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxful_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxless_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_unique_products\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"category\"}}},{\"name\":\"currency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_birth_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_first_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_first_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_first_name\"}}},{\"name\":\"customer_full_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_full_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_full_name\"}}},{\"name\":\"customer_gender\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_last_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_last_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_last_name\"}}},{\"name\":\"customer_phone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week_i\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"manufacturer\"}}},{\"name\":\"order_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"order_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products._id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products._id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products._id\"}}},{\"name\":\"products.base_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.base_unit_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products.category\"}}},{\"name\":\"products.created_on\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_percentage\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products.manufacturer\"}}},{\"name\":\"products.min_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.product_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products.product_name\"}}},{\"name\":\"products.quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.tax_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxful_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxless_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.unit_discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxful_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxless_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_unique_products\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", "fieldFormatMap": "{\"taxful_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.[00]\"}}}" } }, diff --git a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js b/src/legacy/server/sample_data/data_sets/logs/saved_objects.js index 234436349b02f2..522f43d107b24e 100644 --- a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/logs/saved_objects.js @@ -241,7 +241,7 @@ export const getSavedObjects = () => [ "attributes": { "title": "kibana_sample_data_logs", "timeFieldName": "timestamp", - "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"agent\",\"subType\":\"multi\"},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"extension\",\"subType\":\"multi\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"host\",\"subType\":\"multi\"},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"index\",\"subType\":\"multi\"},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"machine.os\",\"subType\":\"multi\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"message\",\"subType\":\"multi\"},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"request\",\"subType\":\"multi\"},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"response\",\"subType\":\"multi\"},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"tags.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"tags\",\"subType\":\"multi\"},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"url\",\"subType\":\"multi\"},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"message\"}}},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"tags.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"tags\"}}},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", "fieldFormatMap": "{\"hour_of_day\":{}}" } }, diff --git a/src/legacy/server/saved_objects/routes/bulk_create.test.ts b/src/legacy/server/saved_objects/routes/bulk_create.test.ts index 1e041bb28f75f5..b49554995aab6b 100644 --- a/src/legacy/server/saved_objects/routes/bulk_create.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_create.test.ts @@ -22,11 +22,11 @@ import { createMockServer } from './_mock_server'; import { createBulkCreateRoute } from './bulk_create'; // Disable lint errors for imports from src/core/* until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsClientMock } from '../../../../core/server/saved_objects/service/saved_objects_client.mock'; +import { savedObjectsClientMock } from '../../../../core/server/saved_objects/service/saved_objects_client.mock'; describe('POST /api/saved_objects/_bulk_create', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.bulkCreate.mockImplementation(() => Promise.resolve('' as any)); diff --git a/src/legacy/server/saved_objects/routes/bulk_get.test.ts b/src/legacy/server/saved_objects/routes/bulk_get.test.ts index 546164be65c9f0..e154649e2cf049 100644 --- a/src/legacy/server/saved_objects/routes/bulk_get.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_get.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createBulkGetRoute } from './bulk_get'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/_bulk_get', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.bulkGet.mockImplementation(() => diff --git a/src/legacy/server/saved_objects/routes/bulk_update.test.ts b/src/legacy/server/saved_objects/routes/bulk_update.test.ts index ee74ddfc535d2c..dc21ab08035ce3 100644 --- a/src/legacy/server/saved_objects/routes/bulk_update.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_update.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createBulkUpdateRoute } from './bulk_update'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('PUT /api/saved_objects/_bulk_update', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { server = createMockServer(); diff --git a/src/legacy/server/saved_objects/routes/create.test.ts b/src/legacy/server/saved_objects/routes/create.test.ts index 85096228c31751..4f096a9ee5c931 100644 --- a/src/legacy/server/saved_objects/routes/create.test.ts +++ b/src/legacy/server/saved_objects/routes/create.test.ts @@ -20,7 +20,7 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createCreateRoute } from './create'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/{type}', () => { let server: Hapi.Server; @@ -32,7 +32,7 @@ describe('POST /api/saved_objects/{type}', () => { references: [], attributes: {}, }; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.create.mockImplementation(() => Promise.resolve(clientResponse)); diff --git a/src/legacy/server/saved_objects/routes/delete.test.ts b/src/legacy/server/saved_objects/routes/delete.test.ts index 9e2adcf9d3b91f..f3e5e837714716 100644 --- a/src/legacy/server/saved_objects/routes/delete.test.ts +++ b/src/legacy/server/saved_objects/routes/delete.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createDeleteRoute } from './delete'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('DELETE /api/saved_objects/{type}/{id}', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.delete.mockImplementation(() => Promise.resolve('{}')); diff --git a/src/legacy/server/saved_objects/routes/export.test.ts b/src/legacy/server/saved_objects/routes/export.test.ts index 2670535ab995ea..93ca3a419e6dff 100644 --- a/src/legacy/server/saved_objects/routes/export.test.ts +++ b/src/legacy/server/saved_objects/routes/export.test.ts @@ -28,14 +28,14 @@ import * as exportMock from '../../../../core/server/saved_objects/export'; import { createMockServer } from './_mock_server'; import { createExportRoute } from './export'; import { createListStream } from '../../../utils/streams'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; const getSortedObjectsForExport = exportMock.getSortedObjectsForExport as jest.Mock; describe('POST /api/saved_objects/_export', () => { let server: Hapi.Server; const savedObjectsClient = { - ...SavedObjectsClientMock.create(), + ...savedObjectsClientMock.create(), errors: {} as any, }; diff --git a/src/legacy/server/saved_objects/routes/find.test.ts b/src/legacy/server/saved_objects/routes/find.test.ts index 89cd0dd28d0356..4bf5f57fec1999 100644 --- a/src/legacy/server/saved_objects/routes/find.test.ts +++ b/src/legacy/server/saved_objects/routes/find.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createFindRoute } from './find'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('GET /api/saved_objects/_find', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); const clientResponse = { total: 0, diff --git a/src/legacy/server/saved_objects/routes/get.test.ts b/src/legacy/server/saved_objects/routes/get.test.ts index 2f7eaea1bc7709..7ede2a8b4d7b4c 100644 --- a/src/legacy/server/saved_objects/routes/get.test.ts +++ b/src/legacy/server/saved_objects/routes/get.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createGetRoute } from './get'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('GET /api/saved_objects/{type}/{id}', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.get.mockImplementation(() => diff --git a/src/legacy/server/saved_objects/routes/import.test.ts b/src/legacy/server/saved_objects/routes/import.test.ts index 1a0684a35ec790..2b8d9d75235077 100644 --- a/src/legacy/server/saved_objects/routes/import.test.ts +++ b/src/legacy/server/saved_objects/routes/import.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createImportRoute } from './import'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/_import', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); const emptyResponse = { saved_objects: [], total: 0, diff --git a/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts b/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts index 7988165207e63a..44fa46bccfce58 100644 --- a/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts +++ b/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createResolveImportErrorsRoute } from './resolve_import_errors'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/_resolve_import_errors', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { server = createMockServer(); diff --git a/src/legacy/server/saved_objects/routes/update.test.ts b/src/legacy/server/saved_objects/routes/update.test.ts index 69a6fe3030009a..aaeaff489d30ab 100644 --- a/src/legacy/server/saved_objects/routes/update.test.ts +++ b/src/legacy/server/saved_objects/routes/update.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createUpdateRoute } from './update'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('PUT /api/saved_objects/{type}/{id?}', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { const clientResponse = { diff --git a/src/legacy/server/usage/README.md b/src/legacy/server/usage/README.md index ad1bb822b70cda..5c4bcc05bbc383 100644 --- a/src/legacy/server/usage/README.md +++ b/src/legacy/server/usage/README.md @@ -90,5 +90,3 @@ There are a few ways you can test that your usage collector is working properly. Yes. When you talk to the Platform team about new fields being added, point out specifically which properties will have dynamic inner fields. 5. **If I accumulate an event counter in server memory, which my fetch method returns, won't it reset when the Kibana server restarts?** Yes, but that is not a major concern. A visualization on such info might be a date histogram that gets events-per-second or something, which would be impacted by server restarts, so we'll have to offset the beginning of the time range when we detect that the latest metric is smaller than the earliest metric. That would be a pretty custom visualization, but perhaps future Kibana enhancements will be able to support that. -6. **Who can I talk to with more questions?** - The Kibana Platform team is the owner of the telemetry service. You can bring questions to them. You can also talk to Tim Sullivan, who created the Kibana telemetry service, or Chris Earle, who set up the telemetry cluster and AWS Lambas for the upstream prod and staging endpoints that recieve the data sent from end-user browsers. diff --git a/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js b/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js index 20ff1d5bcfe0a0..a58ec992495fd3 100644 --- a/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js +++ b/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js @@ -69,7 +69,7 @@ describe('UiExports', function () { sandbox .stub(getUiSettingsServiceForRequestNS, 'getUiSettingsServiceForRequest') .returns({ - getDefaults: noop, + getRegistered: noop, getUserProvided: noop }); }); diff --git a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts index 27b3967d6f46e2..66466b96abe837 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts @@ -28,9 +28,9 @@ export function fieldFormatsMixin(kbnServer: any, server: Legacy.Server) { // for use outside of the request context, for special cases server.decorate('server', 'fieldFormatServiceFactory', async function(uiSettings) { const uiConfigs = await uiSettings.getAll(); - const uiSettingDefaults = uiSettings.getDefaults(); - Object.keys(uiSettingDefaults).forEach(key => { - if (has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') { + const registeredUiSettings = uiSettings.getRegistered(); + Object.keys(registeredUiSettings).forEach(key => { + if (has(uiConfigs, key) && registeredUiSettings[key].type === 'json') { uiConfigs[key] = JSON.parse(uiConfigs[key]); } }); diff --git a/src/legacy/ui/public/agg_types/__tests__/agg_param_writer.js b/src/legacy/ui/public/agg_types/__tests__/agg_param_writer.js deleted file mode 100644 index c85899ca5704e1..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/agg_param_writer.js +++ /dev/null @@ -1,103 +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 _ from 'lodash'; -import { VisProvider } from '../../vis'; -import { aggTypes } from '..'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { AggGroupNames } from '../../vis/editors/default/agg_groups'; - -// eslint-disable-next-line import/no-default-export -export default function AggParamWriterHelper(Private) { - const Vis = Private(VisProvider); - const stubbedLogstashIndexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - /** - * Helper object for writing aggParams. Specify an aggType and it will find a vis & schema, and - * wire up the supporting objects required to feed in parameters, and get #write() output. - * - * Use cases: - * - Verify that the interval parameter of the histogram visualization casts its input to a number - * ```js - * it('casts to a number', function () { - * let writer = new AggParamWriter({ aggType: 'histogram' }); - * let output = writer.write({ interval : '100/10' }); - * expect(output.params.interval).to.be.a('number'); - * expect(output.params.interval).to.be(100); - * }); - * ``` - * - * @class AggParamWriter - * @param {object} opts - describe the properties of this paramWriter - * @param {string} opts.aggType - the name of the aggType we want to test. ('histogram', 'filter', etc.) - */ - class AggParamWriter { - - constructor(opts) { - this.aggType = opts.aggType; - if (_.isString(this.aggType)) { - this.aggType = aggTypes.buckets.find(agg => agg.name === this.aggType) || aggTypes.metrics.find(agg => agg.name === this.aggType); - } - - // not configurable right now, but totally required - this.indexPattern = stubbedLogstashIndexPattern; - - // the schema that the aggType satisfies - this.visAggSchema = null; - - this.vis = new Vis(this.indexPattern, { - type: 'histogram', - aggs: [{ - id: 1, - type: this.aggType.name, - params: {} - }] - }); - } - - write(paramValues, modifyAggConfig = null) { - paramValues = _.clone(paramValues); - - if (this.aggType.paramByName('field') && !paramValues.field) { - // pick a field rather than force a field to be specified everywhere - if (this.aggType.type === AggGroupNames.Metrics) { - paramValues.field = _.sample(this.indexPattern.fields.getByType('number')); - } else { - const type = this.aggType.paramByName('field').filterFieldTypes || 'string'; - let field; - do { - field = _.sample(this.indexPattern.fields.getByType(type)); - } while (!field.aggregatable); - paramValues.field = field.name; - } - } - - const aggConfig = this.vis.aggs.aggs[0]; - aggConfig.setParams(paramValues); - - if (modifyAggConfig) { - modifyAggConfig(aggConfig); - } - - return aggConfig.write(this.vis.aggs); - } - } - - return AggParamWriter; -} diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js deleted file mode 100644 index 94603dfa69a660..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js +++ /dev/null @@ -1,80 +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 { set } from 'lodash'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { aggTypes } from '../..'; -import AggParamWriterProvider from '../agg_param_writer'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import chrome from '../../../chrome'; - -const config = chrome.getUiSettingsClient(); - -describe('date_range params', function () { - let paramWriter; - let timeField; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - const AggParamWriter = Private(AggParamWriterProvider); - const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - timeField = indexPattern.timeFieldName; - paramWriter = new AggParamWriter({ aggType: 'date_range' }); - })); - - describe('getKey', () => { - const dateRange = aggTypes.buckets.find(agg => agg.name === 'date_range'); - it('should return object', () => { - const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' }; - expect(dateRange.getKey(bucket)).to.equal({ from: 'from-date', to: 'to-date' }); - }); - }); - - describe('time_zone', () => { - beforeEach(() => { - sinon.stub(config, 'get'); - sinon.stub(config, 'isDefault'); - }); - - it('should use the specified time_zone', () => { - const output = paramWriter.write({ time_zone: 'Europe/Kiev' }); - expect(output.params).to.have.property('time_zone', 'Europe/Kiev'); - }); - - it('should use the Kibana time_zone if no parameter specified', () => { - config.isDefault.withArgs('dateFormat:tz').returns(false); - config.get.withArgs('dateFormat:tz').returns('Europe/Riga'); - const output = paramWriter.write({}); - expect(output.params).to.have.property('time_zone', 'Europe/Riga'); - }); - - it('should use the fixed time_zone from the index pattern typeMeta', () => { - set(paramWriter.indexPattern, ['typeMeta', 'aggs', 'date_range', timeField, 'time_zone'], 'Europe/Rome'); - const output = paramWriter.write({ field: timeField }); - expect(output.params).to.have.property('time_zone', 'Europe/Rome'); - }); - - afterEach(() => { - config.get.restore(); - config.isDefault.restore(); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_geo_hash.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_geo_hash.js deleted file mode 100644 index 7172d1f40936ef..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/_geo_hash.js +++ /dev/null @@ -1,257 +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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import { geoHashBucketAgg } from '../../buckets/geo_hash'; -import * as AggConfigModule from '../../agg_config'; -import * as BucketAggTypeModule from '../../buckets/_bucket_agg_type'; - -describe('Geohash Agg', () => { - - const initialZoom = 10; - const initialMapBounds = { - top_left: { lat: 1.0, lon: -1.0 }, - bottom_right: { lat: -1.0, lon: 1.0 } - }; - - const BucketAggTypeMock = (aggOptions) => { - return aggOptions; - }; - const AggConfigMock = (parent, aggOptions) => { - return aggOptions; - }; - const createAggregationMock = (aggOptions) => { - return new AggConfigMock(null, aggOptions); - }; - - const aggMock = { - getField: () => { - return { - name: 'location' - }; - }, - params: { - isFilteredByCollar: true, - useGeocentroid: true, - mapZoom: initialZoom - }, - aggConfigs: {}, - type: 'geohash_grid', - }; - aggMock.aggConfigs.createAggConfig = createAggregationMock; - - - before(function () { - sinon.stub(AggConfigModule, 'AggConfig').callsFake(AggConfigMock); - sinon.stub(BucketAggTypeModule, 'BucketAggType').callsFake(BucketAggTypeMock); - }); - - after(function () { - AggConfigModule.AggConfig.restore(); - BucketAggTypeModule.BucketAggType.restore(); - }); - - function initAggParams() { - aggMock.params.isFilteredByCollar = true; - aggMock.params.useGeocentroid = true; - aggMock.params.mapBounds = initialMapBounds; - } - - function zoomMap(zoomChange) { - aggMock.params.mapZoom += zoomChange; - } - - function moveMap(newBounds) { - aggMock.params.mapBounds = newBounds; - } - - function resetMap() { - aggMock.params.mapZoom = initialZoom; - aggMock.params.mapBounds = initialMapBounds; - aggMock.params.mapCollar = { - top_left: { lat: 1.5, lon: -1.5 }, - bottom_right: { lat: -1.5, lon: 1.5 }, - zoom: initialZoom - }; - } - - describe('precision parameter', () => { - - const PRECISION_PARAM_INDEX = 2; - let precisionParam; - beforeEach(() => { - precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX]; - }); - - it('should select precision parameter', () => { - expect(precisionParam.name).to.equal('precision'); - }); - - describe('precision parameter write', () => { - - const zoomToGeoHashPrecision = { - 0: 1, - 1: 2, - 2: 2, - 3: 2, - 4: 3, - 5: 3, - 6: 4, - 7: 4, - 8: 4, - 9: 5, - 10: 5, - 11: 6, - 12: 6, - 13: 6, - 14: 7, - 15: 7, - 16: 7, - 17: 7, - 18: 7, - 19: 7, - 20: 7, - 21: 7 - }; - - Object.keys(zoomToGeoHashPrecision).forEach((zoomLevel) => { - it(`zoom level ${zoomLevel} should correspond to correct geohash-precision`, () => { - const output = { params: {} }; - precisionParam.write({ - params: { - autoPrecision: true, - mapZoom: zoomLevel - } - }, output); - expect(output.params.precision).to.equal(zoomToGeoHashPrecision[zoomLevel]); - }); - }); - }); - - }); - - describe('getRequestAggs', () => { - - describe('initial aggregation creation', () => { - let requestAggs; - beforeEach(() => { - initAggParams(); - requestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - }); - - it('should create filter, geohash_grid, and geo_centroid aggregations', () => { - expect(requestAggs.length).to.equal(3); - expect(requestAggs[0].type).to.equal('filter'); - expect(requestAggs[1].type).to.equal('geohash_grid'); - expect(requestAggs[2].type).to.equal('geo_centroid'); - }); - - it('should set mapCollar in vis session state', () => { - expect(aggMock).to.have.property('lastMapCollar'); - expect(aggMock.lastMapCollar).to.have.property('top_left'); - expect(aggMock.lastMapCollar).to.have.property('bottom_right'); - expect(aggMock.lastMapCollar).to.have.property('zoom'); - }); - - // there was a bug because of an "&& mapZoom" check which excluded 0 as a valid mapZoom, but it is. - it('should create filter, geohash_grid, and geo_centroid aggregations when zoom level 0', () => { - aggMock.params.mapZoom = 0; - requestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - expect(requestAggs.length).to.equal(3); - expect(requestAggs[0].type).to.equal('filter'); - expect(requestAggs[1].type).to.equal('geohash_grid'); - expect(requestAggs[2].type).to.equal('geo_centroid'); - }); - }); - - describe('aggregation options', () => { - - beforeEach(() => { - initAggParams(); - }); - - it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { - aggMock.params.isFilteredByCollar = false; - const requestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - expect(requestAggs.length).to.equal(2); - expect(requestAggs[0].type).to.equal('geohash_grid'); - expect(requestAggs[1].type).to.equal('geo_centroid'); - }); - - it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { - aggMock.params.useGeocentroid = false; - const requestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - expect(requestAggs.length).to.equal(2); - expect(requestAggs[0].type).to.equal('filter'); - expect(requestAggs[1].type).to.equal('geohash_grid'); - - }); - }); - - describe('aggregation creation after map interaction', () => { - - let origRequestAggs; - let origMapCollar; - beforeEach(() => { - resetMap(); - initAggParams(); - origRequestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - origMapCollar = JSON.stringify(aggMock.lastMapCollar, null, ''); - }); - - it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { - moveMap({ - top_left: { lat: 1.1, lon: -1.1 }, - bottom_right: { lat: -0.9, lon: 0.9 } - }); - - const newRequestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - expect(JSON.stringify(origRequestAggs[0].params, null, '')).to.equal(JSON.stringify(newRequestAggs[0].params, null, '')); - - const newMapCollar = JSON.stringify(aggMock.lastMapCollar, null, ''); - expect(origMapCollar).to.equal(newMapCollar); - }); - - it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { - moveMap({ - top_left: { lat: 10.0, lon: -10.0 }, - bottom_right: { lat: 9.0, lon: -9.0 } - }); - - const newRequestAggs = geoHashBucketAgg.getRequestAggs(aggMock); - expect(JSON.stringify(origRequestAggs[0].params, null, '')).not.to.equal(JSON.stringify(newRequestAggs[0].params, null, '')); - - const newMapCollar = JSON.stringify(aggMock.lastMapCollar, null, ''); - expect(origMapCollar).not.to.equal(newMapCollar); - }); - - it('should change geo_bounding_box filter aggregation and vis session state when map zoom level changes', () => { - zoomMap(-1); - - geoHashBucketAgg.getRequestAggs(aggMock); - - const newMapCollar = JSON.stringify(aggMock.lastMapCollar, null, ''); - expect(origMapCollar).not.to.equal(newMapCollar); - }); - - }); - - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_histogram.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_histogram.js deleted file mode 100644 index 26ad80e28ae9bf..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/_histogram.js +++ /dev/null @@ -1,210 +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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { aggTypes } from '../..'; -import chrome from '../../../chrome'; -import AggParamWriterProvider from '../agg_param_writer'; - -const config = chrome.getUiSettingsClient(); -const histogram = aggTypes.buckets.find(agg => agg.name === 'histogram'); -describe('Histogram Agg', function () { - - describe('ordered', function () { - - it('is ordered', function () { - expect(histogram.ordered).to.be.ok(); - }); - - it('is not ordered by date', function () { - expect(histogram.ordered).to.not.have.property('date'); - }); - }); - - - describe('params', function () { - let paramWriter; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - const AggParamWriter = Private(AggParamWriterProvider); - paramWriter = new AggParamWriter({ aggType: 'histogram' }); - })); - - describe('intervalBase', () => { - it('should not be written to the DSL', () => { - const output = paramWriter.write({ intervalBase: 100 }); - expect(output.params).not.to.have.property('intervalBase'); - }); - }); - - describe('interval', function () { - // reads aggConfig.params.interval, writes to dsl.interval - - it('accepts a whole number', function () { - const output = paramWriter.write({ interval: 100 }); - expect(output.params).to.have.property('interval', 100); - }); - - it('accepts a decimal number', function () { - const output = paramWriter.write({ interval: 0.1 }); - expect(output.params).to.have.property('interval', 0.1); - }); - - it('accepts a decimal number string', function () { - const output = paramWriter.write({ interval: '0.1' }); - expect(output.params).to.have.property('interval', 0.1); - }); - - it('accepts a whole number string', function () { - const output = paramWriter.write({ interval: '10' }); - expect(output.params).to.have.property('interval', 10); - }); - - it('fails on non-numeric values', function () { - // template validation prevents this from users, not devs - const output = paramWriter.write({ interval: [] }); - expect(isNaN(output.params.interval)).to.be.ok(); - }); - - describe('interval scaling', () => { - - beforeEach(() => { - sinon.stub(config, 'get'); - }); - - it('will respect the histogram:maxBars setting', () => { - config.get.withArgs('histogram:maxBars').returns(5); - const output = paramWriter.write({ interval: 5 }, - aggConfig => aggConfig.setAutoBounds({ min: 0, max: 10000 })); - expect(output.params).to.have.property('interval', 2000); - }); - - it('will return specified interval, if bars are below histogram:maxBars config', () => { - config.get.withArgs('histogram:maxBars').returns(10000); - const output = paramWriter.write({ interval: 5 }, - aggConfig => aggConfig.setAutoBounds({ min: 0, max: 10000 })); - expect(output.params).to.have.property('interval', 5); - }); - - it('will set to intervalBase if interval is below base', () => { - const output = paramWriter.write({ interval: 3, intervalBase: 8 }); - expect(output.params).to.have.property('interval', 8); - }); - - it('will round to nearest intervalBase multiple if interval is above base', () => { - const roundUp = paramWriter.write({ interval: 46, intervalBase: 10 }); - expect(roundUp.params).to.have.property('interval', 50); - const roundDown = paramWriter.write({ interval: 43, intervalBase: 10 }); - expect(roundDown.params).to.have.property('interval', 40); - }); - - it('will not change interval if it is a multiple of base', () => { - const output = paramWriter.write({ interval: 35, intervalBase: 5 }); - expect(output.params).to.have.property('interval', 35); - }); - - it('will round to intervalBase after scaling histogram:maxBars', () => { - config.get.withArgs('histogram:maxBars').returns(100); - const output = paramWriter.write({ interval: 5, intervalBase: 6 }, - aggConfig => aggConfig.setAutoBounds({ min: 0, max: 1000 })); - // 100 buckets in 0 to 1000 would result in an interval of 10, so we should - // round to the next multiple of 6 -> 12 - expect(output.params).to.have.property('interval', 12); - }); - - afterEach(() => { - config.get.restore(); - }); - }); - }); - - describe('min_doc_count', function () { - it('casts true values to 0', function () { - let output = paramWriter.write({ min_doc_count: true }); - expect(output.params).to.have.property('min_doc_count', 0); - - output = paramWriter.write({ min_doc_count: 'yes' }); - expect(output.params).to.have.property('min_doc_count', 0); - - output = paramWriter.write({ min_doc_count: 1 }); - expect(output.params).to.have.property('min_doc_count', 0); - - output = paramWriter.write({ min_doc_count: {} }); - expect(output.params).to.have.property('min_doc_count', 0); - }); - - it('writes 1 for falsy values', function () { - let output = paramWriter.write({ min_doc_count: '' }); - expect(output.params).to.have.property('min_doc_count', 1); - - output = paramWriter.write({ min_doc_count: null }); - expect(output.params).to.have.property('min_doc_count', 1); - - output = paramWriter.write({ min_doc_count: undefined }); - expect(output.params).to.have.property('min_doc_count', 1); - }); - }); - - describe('extended_bounds', function () { - it('does not write when only eb.min is set', function () { - const output = paramWriter.write({ - has_extended_bounds: true, - extended_bounds: { min: 0 } - }); - expect(output.params).not.to.have.property('extended_bounds'); - }); - - it('does not write when only eb.max is set', function () { - const output = paramWriter.write({ - has_extended_bounds: true, - extended_bounds: { max: 0 } - }); - expect(output.params).not.to.have.property('extended_bounds'); - }); - - it('writes when both eb.min and eb.max are set', function () { - const output = paramWriter.write({ - has_extended_bounds: true, - extended_bounds: { min: 99, max: 100 } - }); - expect(output.params.extended_bounds).to.have.property('min', 99); - expect(output.params.extended_bounds).to.have.property('max', 100); - }); - - it('does not write when nothing is set', function () { - const output = paramWriter.write({ - has_extended_bounds: true, - extended_bounds: {} - }); - expect(output.params).to.not.have.property('extended_bounds'); - }); - - it('does not write when has_extended_bounds is false', function () { - const output = paramWriter.write({ - has_extended_bounds: false, - extended_bounds: { min: 99, max: 100 } - }); - expect(output.params).to.not.have.property('extended_bounds'); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_range.js deleted file mode 100644 index e47802aa6f4bfb..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/_range.js +++ /dev/null @@ -1,71 +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 { values } from 'lodash'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import resp from 'fixtures/agg_resp/range'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('Range Agg', function () { - const buckets = values(resp.aggregations[1].buckets); - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - indexPattern.stubSetFieldFormat('bytes', 'bytes', { - pattern: '0,0.[000] b' - }); - })); - - describe('formating', function () { - it('formats bucket keys properly', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'range', - schema: 'segment', - params: { - field: 'bytes', - ranges: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 } - ] - } - } - ] - }); - - const agg = vis.aggs.byName('range')[0]; - const format = function (val) { - return agg.fieldFormatter()(agg.getKey(val)); - }; - expect(format(buckets[0])).to.be('≥ -∞ and < 1 KB'); - expect(format(buckets[1])).to.be('≥ 1 KB and < 2.5 KB'); - expect(format(buckets[2])).to.be('≥ 2.5 KB and < +∞'); - - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js deleted file mode 100644 index 11e410c43b5921..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js +++ /dev/null @@ -1,121 +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 _ from 'lodash'; -import moment from 'moment'; -import aggResp from 'fixtures/agg_resp/date_histogram'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterDateHistogram } from '../../../buckets/create_filter/date_histogram'; -import { intervalOptions } from '../../../buckets/_interval_options'; - -describe('AggConfig Filters', function () { - describe('date_histogram', function () { - let vis; - let agg; - let field; - let filter; - let bucketKey; - let bucketStart; - - let init; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - init = function (interval, duration) { - interval = interval || 'auto'; - if (interval === 'custom') interval = agg.params.customInterval; - duration = duration || moment.duration(15, 'minutes'); - field = _.sample(_.reject(indexPattern.fields.getByType('date'), 'scripted')); - vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - params: { field: field.name, interval: interval, customInterval: '5d' } - } - ] - }); - - agg = vis.aggs.aggs[0]; - bucketKey = _.sample(aggResp.aggregations['1'].buckets).key; - bucketStart = moment(bucketKey); - - const timePad = moment.duration(duration / 2); - agg.buckets.setBounds({ - min: bucketStart.clone().subtract(timePad), - max: bucketStart.clone().add(timePad), - }); - agg.buckets.setInterval(interval); - - filter = createFilterDateHistogram(agg, bucketKey); - }; - })); - - it('creates a valid range filter', function () { - init(); - - expect(filter).to.have.property('range'); - expect(filter.range).to.have.property(field.name); - - const fieldParams = filter.range[field.name]; - expect(fieldParams).to.have.property('gte'); - expect(fieldParams.gte).to.be.a('string'); - - expect(fieldParams).to.have.property('lt'); - expect(fieldParams.lt).to.be.a('string'); - - expect(fieldParams).to.have.property('format'); - expect(fieldParams.format).to.be('strict_date_optional_time'); - - expect(fieldParams.gte).to.be.lessThan(fieldParams.lt); - - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', vis.indexPattern.id); - }); - - - it('extends the filter edge to 1ms before the next bucket for all interval options', function () { - intervalOptions.forEach(function (option) { - let duration; - if (option.val !== 'custom' && moment(1, option.val).isValid()) { - duration = moment.duration(10, option.val); - - if (+duration < 10) { - throw new Error('unable to create interval for ' + option.val); - } - } - - init(option.val, duration); - - const interval = agg.buckets.getInterval(); - const params = filter.range[field.name]; - - expect(params.gte).to.be(bucketStart.toISOString()); - expect(params.lt).to.be(bucketStart.clone().add(interval).toISOString()); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js deleted file mode 100644 index 3ba03f232428ff..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js +++ /dev/null @@ -1,67 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import moment from 'moment'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterDateRange } from '../../../buckets/create_filter/date_range'; - -describe('AggConfig Filters', function () { - describe('Date range', function () { - let indexPattern; - let Vis; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('should return a range filter for date_range agg', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_range', - params: { - field: '@timestamp', - ranges: [ - { from: '2014-01-01', to: '2014-12-31' } - ] - } - } - ] - }); - - const aggConfig = vis.aggs.byName('date_range')[0]; - const from = new Date('1 Feb 2015'); - const to = new Date('7 Feb 2015'); - const filter = createFilterDateRange(aggConfig, { from: from.valueOf(), to: to.valueOf() }); - expect(filter).to.have.property('range'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.range).to.have.property('@timestamp'); - expect(filter.range['@timestamp']).to.have.property('gte', moment(from).toISOString()); - expect(filter.range['@timestamp']).to.have.property('lt', moment(to).toISOString()); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/filters.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/filters.js deleted file mode 100644 index 409c9a40b19c47..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/filters.js +++ /dev/null @@ -1,61 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterFilters } from '../../../buckets/create_filter/filters'; - -describe('AggConfig Filters', function () { - describe('filters', function () { - let indexPattern; - let Vis; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('should return a filters filter', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'filters', - schema: 'segment', - params: { - filters: [ - { input: { query: 'type:apache', language: 'lucene' } }, - { input: { query: 'type:nginx', language: 'lucene' } } - ] - } - } - ] - }); - - const aggConfig = vis.aggs.byName('filters')[0]; - const filter = createFilterFilters(aggConfig, 'type:nginx'); - expect(filter.query.bool.must[0].query_string.query).to.be('type:nginx'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.meta).to.have.property('alias', 'type:nginx'); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/histogram.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/histogram.js deleted file mode 100644 index 6d4534bba4dd1f..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/histogram.js +++ /dev/null @@ -1,61 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterHistogram } from '../../../buckets/create_filter/histogram'; - -describe('AggConfig Filters', function () { - describe('histogram', function () { - let indexPattern; - let Vis; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('should return an range filter for histogram', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'histogram', - schema: 'segment', - params: { field: 'bytes', interval: 1024 } - } - ] - }); - - const aggConfig = vis.aggs.byName('histogram')[0]; - const filter = createFilterHistogram(aggConfig, 2048); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter).to.have.property('range'); - expect(filter.range).to.have.property('bytes'); - expect(filter.range.bytes).to.have.property('gte', 2048); - expect(filter.range.bytes).to.have.property('lt', 3072); - expect(filter.meta).to.have.property('formattedValue', '2,048'); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js deleted file mode 100644 index e29ebd689db200..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js +++ /dev/null @@ -1,97 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterIpRange } from '../../../buckets/create_filter/ip_range'; -describe('AggConfig Filters', function () { - - describe('IP range', function () { - let indexPattern; - let Vis; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('should return a range filter for ip_range agg', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'ip_range', - schema: 'segment', - params: { - field: 'ip', - ipRangeType: 'fromTo', - ranges: { - fromTo: [ - { from: '0.0.0.0', to: '1.1.1.1' } - ] - } - } - } - ] - }); - - const aggConfig = vis.aggs.byName('ip_range')[0]; - const filter = createFilterIpRange(aggConfig, { type: 'fromTo', from: '0.0.0.0', to: '1.1.1.1' }); - expect(filter).to.have.property('range'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.range).to.have.property('ip'); - expect(filter.range.ip).to.have.property('gte', '0.0.0.0'); - expect(filter.range.ip).to.have.property('lte', '1.1.1.1'); - }); - - it('should return a range filter for ip_range agg using a CIDR mask', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'ip_range', - schema: 'segment', - params: { - field: 'ip', - ipRangeType: 'mask', - ranges: { - mask: [ - { mask: '67.129.65.201/27' } - ] - } - } - } - ] - }); - - const aggConfig = vis.aggs.byName('ip_range')[0]; - const filter = createFilterIpRange(aggConfig, { type: 'mask', mask: '67.129.65.201/27' }); - expect(filter).to.have.property('range'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.range).to.have.property('ip'); - expect(filter.range.ip).to.have.property('gte', '67.129.65.192'); - expect(filter.range.ip).to.have.property('lte', '67.129.65.223'); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/range.js deleted file mode 100644 index 228fd6bce5cfbc..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/range.js +++ /dev/null @@ -1,66 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterRange } from '../../../buckets/create_filter/range'; - -describe('AggConfig Filters', function () { - - describe('range', function () { - let indexPattern; - let Vis; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('should return a range filter for range agg', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'range', - schema: 'segment', - params: { - field: 'bytes', - ranges: [ - { from: 1024, to: 2048 } - ] - } - } - ] - }); - - const aggConfig = vis.aggs.byName('range')[0]; - const filter = createFilterRange(aggConfig, { gte: 1024, lt: 2048.0 }); - expect(filter).to.have.property('range'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.range).to.have.property('bytes'); - expect(filter.range.bytes).to.have.property('gte', 1024.0); - expect(filter.range.bytes).to.have.property('lt', 2048.0); - expect(filter.meta).to.have.property('formattedValue', '≥ 1,024 and < 2,048'); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/terms.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/terms.js deleted file mode 100644 index a2812ffb979651..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/terms.js +++ /dev/null @@ -1,104 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createFilterTerms } from '../../../buckets/create_filter/terms'; - -describe('AggConfig Filters', function () { - - describe('terms', function () { - let indexPattern; - let Vis; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('should return a match_phrase filter for terms', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ { type: 'terms', schema: 'segment', params: { field: '_type' } } ] - }); - const aggConfig = vis.aggs.byName('terms')[0]; - const filter = createFilterTerms(aggConfig, 'apache'); - expect(filter).to.have.property('query'); - expect(filter.query).to.have.property('match_phrase'); - expect(filter.query.match_phrase).to.have.property('_type'); - expect(filter.query.match_phrase._type).to.be('apache'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - - }); - - it('should set query to true or false for boolean filter', () => { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ { type: 'terms', schema: 'segment', params: { field: 'ssl' } } ] - }); - const aggConfig = vis.aggs.byName('terms')[0]; - const filterFalse = createFilterTerms(aggConfig, 0); - expect(filterFalse).to.have.property('query'); - expect(filterFalse.query).to.have.property('match_phrase'); - expect(filterFalse.query.match_phrase).to.have.property('ssl'); - expect(filterFalse.query.match_phrase.ssl).to.be(false); - - const filterTrue = createFilterTerms(aggConfig, 1); - expect(filterTrue).to.have.property('query'); - expect(filterTrue.query).to.have.property('match_phrase'); - expect(filterTrue.query.match_phrase).to.have.property('ssl'); - expect(filterTrue.query.match_phrase.ssl).to.be(true); - }); - - it('should generate correct __missing__ filter', () => { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ { type: 'terms', schema: 'segment', params: { field: '_type' } } ] - }); - const aggConfig = vis.aggs.byName('terms')[0]; - const filter = createFilterTerms(aggConfig, '__missing__'); - expect(filter).to.have.property('exists'); - expect(filter.exists).to.have.property('field', '_type'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.meta).to.have.property('negate', true); - }); - - it('should generate correct __other__ filter', () => { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ { type: 'terms', schema: 'segment', params: { field: '_type' } } ] - }); - const aggConfig = vis.aggs.byName('terms')[0]; - const filter = createFilterTerms(aggConfig, '__other__', { terms: ['apache'] })[0]; - expect(filter).to.have.property('query'); - expect(filter.query).to.have.property('bool'); - expect(filter.query.bool).to.have.property('should'); - expect(filter.query.bool.should[0]).to.have.property('match_phrase'); - expect(filter.query.bool.should[0].match_phrase).to.have.property('_type', 'apache'); - expect(filter).to.have.property('meta'); - expect(filter.meta).to.have.property('index', indexPattern.id); - expect(filter.meta).to.have.property('negate', true); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js b/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js deleted file mode 100644 index 7b8f099a1f9b41..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js +++ /dev/null @@ -1,124 +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 _ from 'lodash'; -import $ from 'jquery'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { VisProvider } from '../../../../vis'; -import { intervalOptions } from '../../../buckets/_interval_options'; - -describe.skip('editor', function () { - - let indexPattern; - let vis; - let agg; - let render; - let $scope; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private, $injector, $compile) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - const Vis = Private(VisProvider); - - /** - * Render the AggParams editor for the date histogram aggregation - * - * @param {object} params - the agg params to give to the date_histogram - * by default - * @return {object} - object pointing to the different inputs, keys - * are the aggParam name and the value is an object - * with $el, $scope, and a few helpers for getting - * data from them. - */ - render = function (params) { - vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { schema: 'metric', type: 'avg', params: { field: 'bytes' } }, - { schema: 'segment', type: 'date_histogram', params: params || {} } - ] - }); - - const $el = $('' + - ''); - const $parentScope = $injector.get('$rootScope').$new(); - - agg = $parentScope.agg = vis.aggs.bySchemaName('segment')[0]; - $parentScope.groupName = 'buckets'; - $parentScope.vis = vis; - - $compile($el)($parentScope); - $scope = $el.scope(); - $scope.$digest(); - - const $inputs = $('vis-agg-param-editor', $el); - return _.transform($inputs.toArray(), function (inputs, e) { - const $el = $(e); - const $scope = $el.scope(); - - inputs[$scope.aggParam.name] = { - $el: $el, - $scope: $scope, - $input: function () { - return $el.find('[ng-model]').first(); - }, - modelValue: function () { - return this.$input().controller('ngModel').$modelValue; - } - }; - }, {}); - }; - - })); - - describe('random field/interval', function () { - let params; - let field; - let interval; - - beforeEach(ngMock.inject(function () { - field = _.sample(indexPattern.fields); - interval = _.sample(intervalOptions); - params = render({ field: field, interval: interval.val }); - })); - - it('renders the field editor', function () { - expect(agg.params.field).to.be(field); - - expect(params).to.have.property('field'); - expect(params.field).to.have.property('$el'); - expect($scope.agg.params.field).to.be(field); - }); - - it('renders the interval editor', function () { - expect(agg.params.interval).to.be(interval.val); - - expect(params).to.have.property('interval'); - expect(params.interval).to.have.property('$el'); - expect($scope.agg.params.interval).to.be(interval.val); - }); - }); - - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_params.js b/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_params.js deleted file mode 100644 index 88646bd36ee803..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_params.js +++ /dev/null @@ -1,215 +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 _ from 'lodash'; -import moment from 'moment'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import AggParamWriterProvider from '../../agg_param_writer'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import chrome from '../../../../chrome'; -import { aggTypes } from '../../..'; -import { AggConfig } from '../../../agg_config'; -import { timefilter } from 'ui/timefilter'; - -const config = chrome.getUiSettingsClient(); - -describe('date_histogram params', function () { - - let paramWriter; - let writeInterval; - let write; - - let getTimeBounds; - let timeField; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - const AggParamWriter = Private(AggParamWriterProvider); - const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - timeField = indexPattern.timeFieldName; - - paramWriter = new AggParamWriter({ aggType: 'date_histogram' }); - writeInterval = function (interval, timeRange, params = {}) { - return paramWriter.write({ ...params, interval: interval, field: timeField, timeRange: timeRange }); - }; - write = (params) => { - return paramWriter.write({ interval: '10s', ...params }); - }; - - const now = moment(); - getTimeBounds = function (n, units) { - timefilter.enableAutoRefreshSelector(); - timefilter.enableTimeRangeSelector(); - return { - from: now.clone().subtract(n, units), - to: now.clone() - }; - }; - })); - - describe('interval', function () { - it('accepts a valid calendar interval', function () { - const output = writeInterval('d'); - expect(output.params).to.have.property('calendar_interval', '1d'); - }); - - it('accepts a valid fixed interval', () => { - const output = writeInterval('100s'); - expect(output.params).to.have.property('fixed_interval', '100s'); - }); - - it('throws error when interval is invalid', function () { - expect(() => writeInterval('foo')).to.throw('TypeError: "foo" is not a valid interval.'); - }); - - it('automatically picks an interval', function () { - const timeBounds = getTimeBounds(15, 'm'); - const output = writeInterval('auto', timeBounds); - expect(output.params).to.have.property('fixed_interval', '30s'); - }); - - it('does not scale down the interval', () => { - const timeBounds = getTimeBounds(1, 'm'); - const output = writeInterval('h', timeBounds); - expect(output.params).to.have.property('calendar_interval', '1h'); - expect(output).not.to.have.property('metricScaleText'); - expect(output).not.to.have.property('metricScale'); - }); - - describe('scaling behavior', () => { - - it('should not scale without scaleMetricValues: true', function () { - const timeBounds = getTimeBounds(30, 'm'); - const output = writeInterval('s', timeBounds); - expect(output.params).to.have.property('fixed_interval', '10s'); - expect(output).not.to.have.property('metricScaleText'); - expect(output).not.to.property('metricScale'); - }); - - describe('only scales when all metrics are sum or count', function () { - const tests = [ - [ false, 'avg', 'count', 'sum' ], - [ true, 'count', 'sum' ], - [ false, 'count', 'cardinality' ] - ]; - - tests.forEach(function (test) { - const should = test.shift(); - const typeNames = test.slice(); - - it(typeNames.join(', ') + ' should ' + (should ? '' : 'not') + ' scale', function () { - const timeBounds = getTimeBounds(1, 'y'); - - const vis = paramWriter.vis; - vis.aggs.aggs.splice(0); - - const histoConfig = new AggConfig(vis.aggs, { - type: aggTypes.buckets.find(agg => agg.name === 'date_histogram'), - schema: 'segment', - params: { interval: 's', field: timeField, timeRange: timeBounds, scaleMetricValues: true } - }); - - vis.aggs.aggs.push(histoConfig); - - typeNames.forEach(function (type) { - vis.aggs.aggs.push(new AggConfig(vis.aggs, { - type: aggTypes.metrics.find(agg => agg.name === type), - schema: 'metric' - })); - }); - - const output = histoConfig.write(vis.aggs); - expect(_.has(output, 'metricScale')).to.be(should); - }); - }); - }); - }); - }); - - describe('time_zone', () => { - beforeEach(() => { - sinon.stub(config, 'get'); - sinon.stub(config, 'isDefault'); - }); - - it('should use the specified time_zone', () => { - const output = write({ time_zone: 'Europe/Kiev' }); - expect(output.params).to.have.property('time_zone', 'Europe/Kiev'); - }); - - it('should use the Kibana time_zone if no parameter specified', () => { - config.isDefault.withArgs('dateFormat:tz').returns(false); - config.get.withArgs('dateFormat:tz').returns('Europe/Riga'); - const output = write({}); - expect(output.params).to.have.property('time_zone', 'Europe/Riga'); - }); - - it('should use the fixed time_zone from the index pattern typeMeta', () => { - _.set(paramWriter.indexPattern, ['typeMeta', 'aggs', 'date_histogram', timeField, 'time_zone'], 'Europe/Rome'); - const output = write({ field: timeField }); - expect(output.params).to.have.property('time_zone', 'Europe/Rome'); - }); - - afterEach(() => { - config.get.restore(); - config.isDefault.restore(); - }); - }); - - describe('extended_bounds', function () { - it('should write a long value if a moment passed in', function () { - const then = moment(0); - const now = moment(500); - const output = write({ - extended_bounds: { - min: then, - max: now - } - }); - - expect(typeof output.params.extended_bounds.min).to.be('number'); - expect(typeof output.params.extended_bounds.max).to.be('number'); - expect(output.params.extended_bounds.min).to.be(then.valueOf()); - expect(output.params.extended_bounds.max).to.be(now.valueOf()); - - - }); - - it('should write a long if a long is passed', function () { - const then = 0; - const now = 500; - const output = write({ - extended_bounds: { - min: then, - max: now - } - }); - - expect(typeof output.params.extended_bounds.min).to.be('number'); - expect(typeof output.params.extended_bounds.max).to.be('number'); - expect(output.params.extended_bounds.min).to.be(then.valueOf()); - expect(output.params.extended_bounds.max).to.be(now.valueOf()); - - - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/significant_terms.js b/src/legacy/ui/public/agg_types/__tests__/buckets/significant_terms.js deleted file mode 100644 index ae52fb476c120d..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/significant_terms.js +++ /dev/null @@ -1,95 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { aggTypes } from '../..'; - -describe('Significant Terms Agg', function () { - - describe('order agg editor UI', function () { - - describe('convert include/exclude from old format', function () { - - let $rootScope; - - function init({ aggParams = {} }) { - ngMock.module('kibana'); - ngMock.inject(function (_$rootScope_) { - const significantTerms = aggTypes.buckets.find(agg => agg.name === 'significant_terms'); - - $rootScope = _$rootScope_; - $rootScope.agg = { - id: 'test', - params: aggParams, - type: significantTerms, - getParam: key => aggParams[key], - }; - }); - } - - function testSerializeAndWrite(aggConfig) { - const includeArg = $rootScope.agg.type.paramByName('include'); - const excludeArg = $rootScope.agg.type.paramByName('exclude'); - - expect(includeArg.serialize(aggConfig.params.include, aggConfig)).to.equal('404'); - expect(excludeArg.serialize(aggConfig.params.exclude, aggConfig)).to.equal('400'); - - const output = { params: {} }; - - includeArg.write(aggConfig, output); - excludeArg.write(aggConfig, output); - - expect(output.params.include).to.equal('404'); - expect(output.params.exclude).to.equal('400'); - } - - it('it doesnt do anything with string type', function () { - init({ - aggParams: { - include: '404', - exclude: '400', - field: { - type: 'string' - }, - } - }); - - testSerializeAndWrite($rootScope.agg); - }); - - it('converts object to string type', function () { - init({ - aggParams: { - include: { - pattern: '404' - }, exclude: { - pattern: '400' - }, - field: { - type: 'string' - }, - } - }); - - testSerializeAndWrite($rootScope.agg); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/terms.js b/src/legacy/ui/public/agg_types/__tests__/buckets/terms.js deleted file mode 100644 index 600323d32d38ce..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/terms.js +++ /dev/null @@ -1,193 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { aggTypes } from '../..'; - -describe('Terms Agg', function () { - describe('order agg editor UI', function () { - - let $rootScope; - - function init({ metricAggs = [], aggParams = {} }) { - ngMock.module('kibana'); - ngMock.inject(function ($controller, _$rootScope_) { - const terms = aggTypes.buckets.find(agg => agg.name === 'terms'); - const orderAggController = terms.paramByName('orderAgg').controller; - - $rootScope = _$rootScope_; - $rootScope.agg = { - id: 'test', - params: aggParams, - type: terms, - vis: { - aggs: [] - }, - getParam: key => aggParams[key], - }; - $rootScope.metricAggs = metricAggs; - $controller(orderAggController, { $scope: $rootScope }); - $rootScope.$digest(); - }); - } - - // should be rewritten after EUIficate order_agg.html - it.skip('selects _key if the selected metric becomes incompatible', function () { - init({ - metricAggs: [ - { - id: 'agg1', - type: { - name: 'count' - } - } - ] - }); - - expect($rootScope.agg.params.orderBy).to.be('agg1'); - $rootScope.metricAggs = [ - { - id: 'agg1', - type: { - name: 'top_hits' - } - } - ]; - $rootScope.$digest(); - expect($rootScope.agg.params.orderBy).to.be('_key'); - }); - - // should be rewritten after EUIficate order_agg.html - it.skip('selects _key if the selected metric is removed', function () { - init({ - metricAggs: [ - { - id: 'agg1', - type: { - name: 'count' - } - } - ] - }); - expect($rootScope.agg.params.orderBy).to.be('agg1'); - $rootScope.metricAggs = []; - $rootScope.$digest(); - expect($rootScope.agg.params.orderBy).to.be('_key'); - }); - - describe.skip('custom field formatter', () => { - beforeEach(() => { - init({ - metricAggs: [ - { - id: 'agg1', - type: { - name: 'count' - } - } - ], - aggParams: { - otherBucketLabel: 'Other', - missingBucketLabel: 'Missing' - } - }); - $rootScope.$digest(); - }); - - it ('converts __other__ key', () => { - const formatter = $rootScope.agg.type.getFormat($rootScope.agg).getConverterFor('text'); - expect(formatter('__other__')).to.be('Other'); - }); - - it ('converts __missing__ key', () => { - const formatter = $rootScope.agg.type.getFormat($rootScope.agg).getConverterFor('text'); - expect(formatter('__missing__')).to.be('Missing'); - }); - }); - - it('adds "custom metric" option'); - it('lists all metric agg responses'); - it('lists individual values of a multi-value metric'); - it('displays a metric editor if "custom metric" is selected'); - it('saves the "custom metric" to state and refreshes from it'); - it('invalidates the form if the metric agg form is not complete'); - - describe.skip('convert include/exclude from old format', function () { - - it('it doesnt do anything with string type', function () { - init({ - aggParams: { - include: '404', - exclude: '400', - field: { - type: 'string' - }, - } - }); - - const aggConfig = $rootScope.agg; - const includeArg = $rootScope.agg.type.params.byName.include; - const excludeArg = $rootScope.agg.type.params.byName.exclude; - - expect(includeArg.serialize(aggConfig.params.include, aggConfig)).to.equal('404'); - expect(excludeArg.serialize(aggConfig.params.exclude, aggConfig)).to.equal('400'); - - const output = { params: {} }; - - includeArg.write(aggConfig, output); - excludeArg.write(aggConfig, output); - - expect(output.params.include).to.equal('404'); - expect(output.params.exclude).to.equal('400'); - }); - - it('converts object to string type', function () { - init({ - aggParams: { - include: { - pattern: '404' - }, exclude: { - pattern: '400' - }, - field: { - type: 'string' - }, - } - }); - - const aggConfig = $rootScope.agg; - const includeArg = $rootScope.agg.type.params.byName.include; - const excludeArg = $rootScope.agg.type.params.byName.exclude; - - expect(includeArg.serialize(aggConfig.params.include, aggConfig)).to.equal('404'); - expect(excludeArg.serialize(aggConfig.params.exclude, aggConfig)).to.equal('400'); - - const output = { params: {} }; - - includeArg.write(aggConfig, output); - excludeArg.write(aggConfig, output); - - expect(output.params.include).to.equal('404'); - expect(output.params.exclude).to.equal('400'); - }); - - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/lib/make_nested_label.js b/src/legacy/ui/public/agg_types/__tests__/metrics/lib/make_nested_label.js deleted file mode 100644 index f4ba5505578742..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/lib/make_nested_label.js +++ /dev/null @@ -1,56 +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 expect from '@kbn/expect'; -import { makeNestedLabel } from '../../../metrics/lib/make_nested_label'; - -describe('metric agg make_nested_label', function () { - - function generateAggConfig(metricLabel) { - return { - params: { - customMetric: { - makeLabel: () => { return metricLabel; } - } - }, - getParam(key) { - return this.params[key]; - } - }; - } - - it('should return a metric label with prefix', function () { - const aggConfig = generateAggConfig('Count'); - const label = makeNestedLabel(aggConfig, 'derivative'); - expect(label).to.eql('Derivative of Count'); - }); - - it('should return a numbered prefix', function () { - const aggConfig = generateAggConfig('Derivative of Count'); - const label = makeNestedLabel(aggConfig, 'derivative'); - expect(label).to.eql('2. derivative of Count'); - }); - - it('should return a prefix with correct order', function () { - const aggConfig = generateAggConfig('3. derivative of Count'); - const label = makeNestedLabel(aggConfig, 'derivative'); - expect(label).to.eql('4. derivative of Count'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/median.js b/src/legacy/ui/public/agg_types/__tests__/metrics/median.js deleted file mode 100644 index df65afa5e42228..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/median.js +++ /dev/null @@ -1,73 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypeMetricMedianProvider class', function () { - let indexPattern; - let aggDsl; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - const vis = new Vis(indexPattern, { - 'title': 'New Visualization', - 'type': 'metric', - 'params': { - 'fontSize': 60 - }, - 'aggs': [ - { - 'id': '1', - 'type': 'median', - 'schema': 'metric', - 'params': { - 'field': 'bytes', - 'percents': [ - 50 - ] - } - } - ], - 'listeners': {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggDsl = aggConfig.toDsl(); - })); - - it('requests the percentiles aggregation in the Elasticsearch query DSL', function () { - expect(Object.keys(aggDsl)[0]).to.be('percentiles'); - }); - - it ('asks Elasticsearch for the 50th percentile', function () { - expect(aggDsl.percentiles.percents).to.eql([50]); - }); - - it ('asks Elasticsearch for array-based values in the aggregation response', function () { - expect(aggDsl.percentiles.keyed).to.be(false); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/parent_pipeline.js b/src/legacy/ui/public/agg_types/__tests__/metrics/parent_pipeline.js deleted file mode 100644 index e4ca6075c624bb..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/parent_pipeline.js +++ /dev/null @@ -1,220 +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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { derivativeMetricAgg } from '../../metrics/derivative'; -import { cumulativeSumMetricAgg } from '../../metrics/cumulative_sum'; -import { movingAvgMetricAgg } from '../../metrics/moving_avg'; -import { serialDiffMetricAgg } from '../../metrics/serial_diff'; -import { VisProvider } from '../../../vis'; -import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -const metrics = [ - { name: 'derivative', title: 'Derivative', agg: derivativeMetricAgg }, - { name: 'cumulative_sum', title: 'Cumulative Sum', agg: cumulativeSumMetricAgg }, - { name: 'moving_avg', title: 'Moving Avg', agg: movingAvgMetricAgg, dslName: 'moving_fn' }, - { name: 'serial_diff', title: 'Serial Diff', agg: serialDiffMetricAgg }, -]; - -describe('parent pipeline aggs', function () { - metrics.forEach(metric => { - describe(`${metric.title} metric`, function () { - - let aggDsl; - let metricAgg; - let aggConfig; - - function init(settings) { - ngMock.module('kibana'); - ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(StubbedIndexPattern); - indexPattern.stubSetFieldFormat('bytes', 'bytes'); - metricAgg = metric.agg; - - const params = settings || { - metricAgg: '1', - customMetric: null - }; - - const vis = new Vis(indexPattern, { - title: 'New Visualization', - type: 'metric', - params: { - fontSize: 60 - }, - aggs: [ - { - id: '1', - type: 'count', - schema: 'metric' - }, - { - id: '2', - type: metric.name, - schema: 'metric', - params - }, - { - id: '3', - type: 'max', - params: { field: '@timestamp' }, - schema: 'metric' - } - ], - listeners: {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = vis.aggs.aggs[1]; - aggDsl = aggConfig.toDsl(vis.aggs); - }); - } - - it(`should return a label prefixed with ${metric.title} of`, function () { - init(); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Count`); - }); - - it(`should return a label ${metric.title} of max bytes`, function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '1-orderAgg', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Max bytes`); - }); - - it(`should return a label prefixed with number of ${metric.title.toLowerCase()}`, function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-orderAgg', - type: metric.name, - params: { - buckets_path: 'custom', - customMetric: { - id: '2-orderAgg-orderAgg', - type: 'count', - schema: 'orderAgg' - } - }, - schema: 'orderAgg' - } - }); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`2. ${metric.title.toLowerCase()} of Count`); - }); - - it('should set parent aggs', function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }); - expect(aggDsl[metric.dslName || metric.name].buckets_path).to.be('2-metric'); - expect(aggDsl.parentAggs['2-metric'].max.field).to.be('bytes'); - }); - - it('should set nested parent aggs', function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: metric.name, - params: { - buckets_path: 'custom', - customMetric: { - id: '2-metric-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }, - schema: 'orderAgg' - } - }); - expect(aggDsl[metric.dslName || metric.name].buckets_path).to.be('2-metric'); - expect(aggDsl.parentAggs['2-metric'][metric.dslName || metric.name].buckets_path).to.be('2-metric-metric'); - }); - - it('should have correct formatter', function () { - init({ - metricAgg: '3' - }); - expect(metricAgg.getFormat(aggConfig).type.id).to.be('date'); - }); - - it('should have correct customMetric nested formatter', function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: metric.name, - params: { - buckets_path: 'custom', - customMetric: { - id: '2-metric-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }, - schema: 'orderAgg' - } - }); - expect(metricAgg.getFormat(aggConfig).type.id).to.be('bytes'); - }); - - it('should call modifyAggConfigOnSearchRequestStart for its customMetric\'s parameters', () => { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }); - - const searchSource = {}; - const customMetricSpy = sinon.spy(); - const customMetric = aggConfig.params.customMetric; - - // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter - customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; - - aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); - }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).to.be(true); - }); - }); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/percentile_ranks.js b/src/legacy/ui/public/agg_types/__tests__/metrics/percentile_ranks.js deleted file mode 100644 index 1f767f88862625..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/percentile_ranks.js +++ /dev/null @@ -1,57 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { percentileRanksMetricAgg } from '../../metrics/percentile_ranks'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypesMetricsPercentileRanksProvider class', function () { - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('uses the custom label if it is set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'my custom field label'; - aggConfig.params.values = [ 5000, 10000 ]; - aggConfig.params.field = { - displayName: 'bytes' - }; - - const responseAggs = percentileRanksMetricAgg.getResponseAggs(aggConfig); - const percentileRankLabelFor5kBytes = responseAggs[0].makeLabel(); - const percentileRankLabelFor10kBytes = responseAggs[1].makeLabel(); - - expect(percentileRankLabelFor5kBytes).to.be('Percentile rank 5,000 of "my custom field label"'); - expect(percentileRankLabelFor10kBytes).to.be('Percentile rank 10,000 of "my custom field label"'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/percentiles.js b/src/legacy/ui/public/agg_types/__tests__/metrics/percentiles.js deleted file mode 100644 index afbf8b95d6e73e..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/percentiles.js +++ /dev/null @@ -1,55 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { percentilesMetricAgg } from '../../metrics/percentiles'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypesMetricsPercentilesProvider class', function () { - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('uses the custom label if it is set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'prince'; - aggConfig.params.percents = [ 95 ]; - aggConfig.params.field = { - displayName: 'bytes' - }; - - const responseAggs = percentilesMetricAgg.getResponseAggs(aggConfig); - const ninetyFifthPercentileLabel = responseAggs[0].makeLabel(); - - expect(ninetyFifthPercentileLabel).to.be('95th percentile of prince'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js b/src/legacy/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js deleted file mode 100644 index aba5db9cedadf6..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js +++ /dev/null @@ -1,166 +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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { bucketSumMetricAgg } from '../../metrics/bucket_sum'; -import { bucketAvgMetricAgg } from '../../metrics/bucket_avg'; -import { bucketMinMetricAgg } from '../../metrics/bucket_min'; -import { bucketMaxMetricAgg } from '../../metrics/bucket_max'; -import { VisProvider } from '../../../vis'; -import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -const metrics = [ - { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, - { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, - { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, - { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, -]; - -describe('sibling pipeline aggs', function () { - metrics.forEach(metric => { - describe(`${metric.title} metric`, function () { - - let aggDsl; - let metricAgg; - let aggConfig; - - function init(settings) { - ngMock.module('kibana'); - ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(StubbedIndexPattern); - indexPattern.stubSetFieldFormat('bytes', 'bytes'); - metricAgg = metric.provider; - - const params = settings || { - customMetric: { - id: '5', - type: 'count', - schema: 'metric' - }, - customBucket: { - id: '6', - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: '10s' } - } - }; - - const vis = new Vis(indexPattern, { - title: 'New Visualization', - type: 'metric', - params: { - fontSize: 60 - }, - aggs: [ - { - id: '1', - type: 'count', - schema: 'metric' - }, - { - id: '2', - type: metric.name, - schema: 'metric', - params - } - ], - listeners: {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = vis.aggs.aggs[1]; - aggDsl = aggConfig.toDsl(vis.aggs); - }); - } - - it(`should return a label prefixed with ${metric.title} of`, function () { - init(); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Count`); - }); - - it('should set parent aggs', function () { - init(); - expect(aggDsl[metric.name].buckets_path).to.be('2-bucket>_count'); - expect(aggDsl.parentAggs['2-bucket'].date_histogram).to.not.be.undefined; - }); - - it('should set nested parent aggs', function () { - init({ - customMetric: { - id: '5', - type: 'avg', - schema: 'metric', - params: { field: 'bytes' }, - }, - customBucket: { - id: '6', - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: '10s', }, - } - }); - expect(aggDsl[metric.name].buckets_path).to.be('2-bucket>2-metric'); - expect(aggDsl.parentAggs['2-bucket'].date_histogram).to.not.be.undefined; - expect(aggDsl.parentAggs['2-bucket'].aggs['2-metric'].avg.field).to.equal('bytes'); - }); - - it('should have correct formatter', function () { - init({ - customMetric: { - id: '5', - type: 'avg', - schema: 'metric', - params: { field: 'bytes' }, - }, - customBucket: { - id: '6', - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: '10s' }, - } - }); - expect(metricAgg.getFormat(aggConfig).type.id).to.be('bytes'); - }); - - it('should call modifyAggConfigOnSearchRequestStart for nested aggs\' parameters', () => { - init(); - - const searchSource = {}; - const customMetricSpy = sinon.spy(); - const customBucketSpy = sinon.spy(); - const { customMetric, customBucket } = aggConfig.params; - - // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter - customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; - customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; - - aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); - }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).to.be(true); - expect(customBucketSpy.calledWith(customBucket, searchSource)).to.be(true); - }); - - }); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/std_deviation.js b/src/legacy/ui/public/agg_types/__tests__/metrics/std_deviation.js deleted file mode 100644 index b31794f6d9ffe2..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/std_deviation.js +++ /dev/null @@ -1,74 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { stdDeviationMetricAgg } from '../../metrics/std_deviation'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypeMetricStandardDeviationProvider class', function () { - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('uses the custom label if it is set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'custom label'; - aggConfig.params.field = { - displayName: 'memory' - }; - - const responseAggs = stdDeviationMetricAgg.getResponseAggs(aggConfig); - const lowerStdDevLabel = responseAggs[0].makeLabel(); - const upperStdDevLabel = responseAggs[1].makeLabel(); - - expect(lowerStdDevLabel).to.be('Lower custom label'); - expect(upperStdDevLabel).to.be('Upper custom label'); - }); - - it('uses the default labels if custom label is not set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.field = { - displayName: 'memory' - }; - - const responseAggs = stdDeviationMetricAgg.getResponseAggs(aggConfig); - const lowerStdDevLabel = responseAggs[0].makeLabel(); - const upperStdDevLabel = responseAggs[1].makeLabel(); - - expect(lowerStdDevLabel).to.be('Lower Standard Deviation of memory'); - expect(upperStdDevLabel).to.be('Upper Standard Deviation of memory'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js b/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js deleted file mode 100644 index 927d73793f4692..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js +++ /dev/null @@ -1,376 +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 _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { topHitMetricAgg } from '../../metrics/top_hit'; -import { VisProvider } from '../../../vis'; -import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -describe('Top hit metric', function () { - let aggDsl; - let aggConfig; - - function init({ field, sortOrder = 'desc', aggregate = 'concat', size = 1 }) { - ngMock.module('kibana'); - ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(StubbedIndexPattern); - - const params = {}; - if (field) { - params.field = field; - } - params.sortOrder = { - value: sortOrder - }; - params.aggregate = { - value: aggregate - }; - params.size = size; - const vis = new Vis(indexPattern, { - title: 'New Visualization', - type: 'metric', - params: { - fontSize: 60 - }, - aggs: [ - { - id: '1', - type: 'top_hits', - schema: 'metric', - params - } - ], - listeners: {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = vis.aggs.aggs[0]; - aggDsl = aggConfig.toDsl(); - }); - } - - it('should return a label prefixed with Last if sorting in descending order', function () { - init({ field: 'bytes' }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('Last bytes'); - }); - - it('should return a label prefixed with First if sorting in ascending order', function () { - init({ - field: 'bytes', - sortOrder: 'asc' - }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('First bytes'); - }); - - it('should request the _source field', function () { - init({ field: '_source' }); - expect(aggDsl.top_hits._source).to.be(true); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - it('requests both source and docvalues_fields for non-text aggregatable fields', function () { - init({ field: 'bytes' }); - expect(aggDsl.top_hits._source).to.be('bytes'); - expect(aggDsl.top_hits.docvalue_fields).to.eql([ { field: 'bytes', format: 'use_field_mapping' } ]); - }); - - it('requests both source and docvalues_fields for date aggregatable fields', function () { - init({ field: '@timestamp' }); - expect(aggDsl.top_hits._source).to.be('@timestamp'); - expect(aggDsl.top_hits.docvalue_fields).to.eql([ { field: '@timestamp', format: 'date_time' } ]); - }); - - it('requests just source for aggregatable text fields', function () { - init({ field: 'machine.os' }); - expect(aggDsl.top_hits._source).to.be('machine.os'); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - it('requests just source for not-aggregatable text fields', function () { - init({ field: 'non-sortable' }); - expect(aggDsl.top_hits._source).to.be('non-sortable'); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - it('requests just source for not-aggregatable, non-text fields', function () { - init({ field: 'hashed' }); - expect(aggDsl.top_hits._source).to.be('hashed'); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - describe('try to get the value from the top hit', function () { - it('should return null if there is no hit', function () { - const bucket = { - '1': { - hits: { - hits: [] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be(null); - }); - - it('should return undefined if the field does not appear in the source', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: 123 - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be(undefined); - }); - - it('should return the field value from the top hit', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - '@tags': 'aaa' - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be('aaa'); - }); - - it('should return the object if the field value is an object', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - '@tags': { - label: 'aaa' - } - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql({ label: 'aaa' }); - }); - - it('should return an array if the field has more than one values', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - '@tags': [ 'aaa', 'bbb' ] - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql([ 'aaa', 'bbb' ]); - }); - - it('should get the value from the doc_values field if the source does not have that field', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - 'machine.os': 'linux' - }, - fields: { - 'machine.os.raw': [ 'linux' ] - } - } - ] - } - } - }; - - init({ field: 'machine.os.raw' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be('linux'); - }); - - it('should return undefined if the field is not in the source nor in the doc_values field', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: 12345 - }, - fields: { - bytes: 12345 - } - } - ] - } - } - }; - - init({ field: 'machine.os.raw' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be(undefined); - }); - - describe('Multivalued field and first/last X docs', function () { - it('should return a label prefixed with Last X docs if sorting in descending order', function () { - init({ - field: 'bytes', - size: 2 - }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('Last 2 bytes'); - }); - - it('should return a label prefixed with First X docs if sorting in ascending order', function () { - init({ - field: 'bytes', - size: 2, - sortOrder: 'asc' - }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('First 2 bytes'); - }); - - [ - { - description: 'concat values with a comma', - type: 'concat', - data: [ 1, 2, 3 ], - result: [ 1, 2, 3 ] - }, - { - description: 'sum up the values', - type: 'sum', - data: [ 1, 2, 3 ], - result: 6 - }, - { - description: 'take the minimum value', - type: 'min', - data: [ 1, 2, 3 ], - result: 1 - }, - { - description: 'take the maximum value', - type: 'max', - data: [ 1, 2, 3 ], - result: 3 - }, - { - description: 'take the average value', - type: 'average', - data: [ 1, 2, 3 ], - result: 2 - }, - { - description: 'support null/undefined', - type: 'min', - data: [ undefined, null ], - result: null - }, - { - description: 'support null/undefined', - type: 'max', - data: [ undefined, null ], - result: null - }, - { - description: 'support null/undefined', - type: 'sum', - data: [ undefined, null ], - result: null - }, - { - description: 'support null/undefined', - type: 'average', - data: [ undefined, null ], - result: null - } - ] - .forEach(agg => { - it(`should return the result of the ${agg.type} aggregation over the last doc - ${agg.description}`, function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: agg.data - } - } - ] - } - } - }; - - init({ field: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql(agg.result); - }); - - it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: _.dropRight(agg.data, 1) - } - }, - { - _source: { - bytes: _.last(agg.data) - } - } - ] - } - } - }; - - init({ field: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql(agg.result); - }); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/utils/_stub_agg_params.js b/src/legacy/ui/public/agg_types/__tests__/utils/_stub_agg_params.js deleted file mode 100644 index f30572bcc0ed54..00000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/utils/_stub_agg_params.js +++ /dev/null @@ -1,67 +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 _ from 'lodash'; -import sinon from 'sinon'; -import { BaseParamType } from '../../param_types/base'; -import { FieldParamType } from '../../param_types/field'; -import { OptionedParamType } from '../../param_types/optioned'; -import { createLegacyClass } from '../../../utils/legacy_class'; - -function ParamClassStub(parent, body) { - const stub = sinon.spy(body || function () { - stub.Super && stub.Super.call(this); - }); - if (parent) createLegacyClass(stub).inherits(parent); - return stub; -} - -/** - * stub all of the param classes, but ensure that they still inherit properly. - * This method should be passed directly to ngMock.inject(); - * - * ```js - * let stubParamClasses = require('./utils/_stub_agg_params'); - * describe('something', function () { - * beforeEach(ngMock.inject(stubParamClasses)); - * }) - * ``` - * - * @param {PrivateLoader} Private - The private module loader, inject by passing this function to ngMock.inject() - * @return {undefined} - */ -// eslint-disable-next-line import/no-default-export -export default function stubParamClasses(Private) { - const BaseAggParam = Private.stub( - BaseParamType, - new ParamClassStub(null, function (config) { - _.assign(this, config); - }) - ); - - Private.stub( - FieldParamType, - new ParamClassStub(BaseAggParam) - ); - - Private.stub( - OptionedParamType, - new ParamClassStub(BaseAggParam) - ); -} diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts index a5b1aa7cf9c0b4..eedfc1cc05a84f 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/ui/public/agg_types/agg_config.ts @@ -332,7 +332,7 @@ export class AggConfig { return this.type.getValue(this, bucket); } - getKey(bucket: any, key: string) { + getKey(bucket: any, key?: string) { if (this.type.getKey) { return this.type.getKey(bucket, key, this); } else { diff --git a/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js b/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js index 5dfa2e3d6ae3f6..70bca2e40ae3fa 100644 --- a/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js +++ b/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js @@ -18,8 +18,9 @@ */ import _ from 'lodash'; -import { buildExistsFilter, buildPhrasesFilter, buildQueryFromFilters } from '@kbn/es-query'; +import { buildQueryFromFilters } from '@kbn/es-query'; import { AggGroupNames } from '../../vis/editors/default/agg_groups'; +import { esFilters } from '../../../../../plugins/data/public'; /** * walks the aggregation DSL and returns DSL starting at aggregation with id of startFromAggId @@ -180,7 +181,7 @@ export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) => agg.buckets.some(bucket => bucket.key === '__missing__') ) { filters.push( - buildExistsFilter( + esFilters.buildExistsFilter( aggWithOtherBucket.params.field, aggWithOtherBucket.params.field.indexPattern ) @@ -232,7 +233,7 @@ export const mergeOtherBucketAggResponse = ( ); const requestFilterTerms = getOtherAggTerms(requestAgg, key, otherAgg); - const phraseFilter = buildPhrasesFilter( + const phraseFilter = esFilters.buildPhrasesFilter( otherAgg.params.field, requestFilterTerms, otherAgg.params.field.indexPattern @@ -243,7 +244,7 @@ export const mergeOtherBucketAggResponse = ( if (aggResultBuckets.some(bucket => bucket.key === '__missing__')) { bucket.filters.push( - buildExistsFilter(otherAgg.params.field, otherAgg.params.field.indexPattern) + esFilters.buildExistsFilter(otherAgg.params.field, otherAgg.params.field.indexPattern) ); } aggResultBuckets.push(bucket); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts new file mode 100644 index 00000000000000..9426df7d34c29e --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts @@ -0,0 +1,122 @@ +/* + * 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 { createFilterDateHistogram } from './date_histogram'; +import { intervalOptions } from '../_interval_options'; +import { AggConfigs } from '../../agg_configs'; +import { IBucketDateHistogramAggConfig } from '../date_histogram'; +import { BUCKET_TYPES } from '../bucket_agg_types'; +import { esFilters } from '../../../../../../plugins/data/public'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('date_histogram', () => { + let agg: IBucketDateHistogramAggConfig; + let filter: esFilters.RangeFilter; + let bucketStart: any; + let field: any; + + const init = (interval: string = 'auto', duration: any = moment.duration(15, 'minutes')) => { + field = { + name: 'date', + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + type: BUCKET_TYPES.DATE_HISTOGRAM, + schema: 'segment', + params: { field: field.name, interval, customInterval: '5d' }, + }, + ], + null + ); + const bucketKey = 1422579600000; + + agg = aggConfigs.aggs[0] as IBucketDateHistogramAggConfig; + bucketStart = moment(bucketKey); + + const timePad = moment.duration(duration / 2); + + agg.buckets.setBounds({ + min: bucketStart.clone().subtract(timePad), + max: bucketStart.clone().add(timePad), + }); + agg.buckets.setInterval(interval); + filter = createFilterDateHistogram(agg, bucketKey); + }; + + it('creates a valid range filter', () => { + init(); + + expect(filter).toHaveProperty('range'); + expect(filter.range).toHaveProperty(field.name); + + const fieldParams = filter.range[field.name]; + expect(fieldParams).toHaveProperty('gte'); + expect(typeof fieldParams.gte).toBe('string'); + + expect(fieldParams).toHaveProperty('lt'); + expect(typeof fieldParams.lt).toBe('string'); + + expect(fieldParams).toHaveProperty('format'); + expect(fieldParams.format).toBe('strict_date_optional_time'); + + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + }); + + it('extends the filter edge to 1ms before the next bucket for all interval options', () => { + intervalOptions.forEach(option => { + let duration; + if (option.val !== 'custom' && moment(1, option.val).isValid()) { + // @ts-ignore + duration = moment.duration(10, option.val); + + if (+duration < 10) { + throw new Error('unable to create interval for ' + option.val); + } + } + init(option.val, duration); + + const interval = agg.buckets.getInterval(); + const params = filter.range[field.name]; + + expect(params.gte).toBe(bucketStart.toISOString()); + expect(params.lt).toBe( + bucketStart + .clone() + .add(interval) + .toISOString() + ); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts index 6bda085335309d..f91a92eab1c33f 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts @@ -18,14 +18,17 @@ */ import moment from 'moment'; -import { buildRangeFilter } from '@kbn/es-query'; import { IBucketDateHistogramAggConfig } from '../date_histogram'; +import { esFilters } from '../../../../../../plugins/data/public'; -export const createFilterDateHistogram = (agg: IBucketDateHistogramAggConfig, key: string) => { +export const createFilterDateHistogram = ( + agg: IBucketDateHistogramAggConfig, + key: string | number +) => { const start = moment(key); const interval = agg.buckets.getInterval(); - return buildRangeFilter( + return esFilters.buildRangeFilter( agg.params.field, { gte: start.toISOString(), diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts new file mode 100644 index 00000000000000..35b6c38bad799f --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { createFilterDateRange } from './date_range'; +import { DateFormat } from '../../../../../../plugins/data/common'; +import { AggConfigs } from '../../agg_configs'; +import { BUCKET_TYPES } from '../bucket_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('Date range', () => { + const getAggConfigs = () => { + const field = { + name: '@timestamp', + format: new DateFormat({}, () => {}), + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + type: BUCKET_TYPES.DATE_RANGE, + params: { + field: '@timestamp', + ranges: [{ from: '2014-01-01', to: '2014-12-31' }], + }, + }, + ], + null + ); + }; + + it('should return a range filter for date_range agg', () => { + const aggConfigs = getAggConfigs(); + const from = new Date('1 Feb 2015'); + const to = new Date('7 Feb 2015'); + const filter = createFilterDateRange(aggConfigs.aggs[0], { + from: from.valueOf(), + to: to.valueOf(), + }); + + expect(filter).toHaveProperty('range'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter.range).toHaveProperty('@timestamp'); + expect(filter.range['@timestamp']).toHaveProperty('gte', moment(from).toISOString()); + expect(filter.range['@timestamp']).toHaveProperty('lt', moment(to).toISOString()); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts index cd4b0ffc215b07..01689d954a0723 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts @@ -17,16 +17,16 @@ * under the License. */ -import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query'; import moment from 'moment'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { DateRangeKey } from '../date_range'; +import { esFilters } from '../../../../../../plugins/data/public'; export const createFilterDateRange = (agg: IBucketAggConfig, { from, to }: DateRangeKey) => { - const filter: RangeFilterParams = {}; + const filter: esFilters.RangeFilterParams = {}; if (from) filter.gte = moment(from).toISOString(); if (to) filter.lt = moment(to).toISOString(); if (to && from) filter.format = 'strict_date_optional_time'; - return buildRangeFilter(agg.params.field, filter, agg.getIndexPattern()); + return esFilters.buildRangeFilter(agg.params.field, filter, agg.getIndexPattern()); }; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts new file mode 100644 index 00000000000000..125532fe070ba8 --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { createFilterFilters } from './filters'; +import { AggConfigs } from '../../agg_configs'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('filters', () => { + const getAggConfigs = () => { + const field = { + name: 'bytes', + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + type: 'filters', + schema: 'segment', + params: { + filters: [ + { input: { query: 'type:apache', language: 'lucene' } }, + { input: { query: 'type:nginx', language: 'lucene' } }, + ], + }, + }, + ], + null + ); + }; + it('should return a filters filter', () => { + const aggConfigs = getAggConfigs(); + const filter = createFilterFilters(aggConfigs.aggs[0], 'type:nginx'); + + expect(filter!.query.bool.must[0].query_string.query).toBe('type:nginx'); + expect(filter!.meta).toHaveProperty('index', '1234'); + expect(filter!.meta).toHaveProperty('alias', 'type:nginx'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts index bf30b333056bcf..6b614514580b6d 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts @@ -18,8 +18,8 @@ */ import { get } from 'lodash'; -import { buildQueryFilter } from '@kbn/es-query'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { esFilters } from '../../../../../../plugins/data/public'; export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => { // have the aggConfig write agg dsl params @@ -28,6 +28,6 @@ export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => const indexPattern = aggConfig.getIndexPattern(); if (filter && indexPattern && indexPattern.id) { - return buildQueryFilter(filter.query, indexPattern.id, key); + return esFilters.buildQueryFilter(filter.query, indexPattern.id, key); } }; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts new file mode 100644 index 00000000000000..0095df75b8914e --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.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 { createFilterHistogram } from './histogram'; +import { AggConfigs } from '../../agg_configs'; +import { BUCKET_TYPES } from '../bucket_agg_types'; +import { BytesFormat } from '../../../../../../plugins/data/common'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('histogram', () => { + const getAggConfigs = () => { + const field = { + name: 'bytes', + format: new BytesFormat({}, () => {}), + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + id: BUCKET_TYPES.HISTOGRAM, + type: BUCKET_TYPES.HISTOGRAM, + schema: 'buckets', + params: { + field: 'bytes', + interval: 1024, + }, + }, + ], + null + ); + }; + + it('should return an range filter for histogram', () => { + const aggConfigs = getAggConfigs(); + const filter = createFilterHistogram(aggConfigs.aggs[0], '2048'); + + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter).toHaveProperty('range'); + expect(filter.range).toHaveProperty('bytes'); + expect(filter.range.bytes).toHaveProperty('gte', 2048); + expect(filter.range.bytes).toHaveProperty('lt', 3072); + expect(filter.meta).toHaveProperty('formattedValue', '2,048'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts index 37d5c6bc8adc1f..fc587fa9ecdb6b 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts @@ -17,14 +17,14 @@ * under the License. */ -import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { esFilters } from '../../../../../../plugins/data/public'; export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => { const value = parseInt(key, 10); - const params: RangeFilterParams = { gte: value, lt: value + aggConfig.params.interval }; + const params: esFilters.RangeFilterParams = { gte: value, lt: value + aggConfig.params.interval }; - return buildRangeFilter( + return esFilters.buildRangeFilter( aggConfig.params.field, params, aggConfig.getIndexPattern(), diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts new file mode 100644 index 00000000000000..2e030d820b3963 --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts @@ -0,0 +1,104 @@ +/* + * 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 { createFilterIpRange } from './ip_range'; +import { AggConfigs } from '../../agg_configs'; +import { IpFormat } from '../../../../../../plugins/data/common'; +import { BUCKET_TYPES } from '../bucket_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('IP range', () => { + const getAggConfigs = (aggs: Array>) => { + const field = { + name: 'ip', + format: IpFormat, + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs(indexPattern, aggs, null); + }; + + it('should return a range filter for ip_range agg', () => { + const aggConfigs = getAggConfigs([ + { + type: BUCKET_TYPES.IP_RANGE, + schema: 'segment', + params: { + field: 'ip', + ipRangeType: 'range', + ranges: { + fromTo: [{ from: '0.0.0.0', to: '1.1.1.1' }], + }, + }, + }, + ]); + + const filter = createFilterIpRange(aggConfigs.aggs[0], { + type: 'range', + from: '0.0.0.0', + to: '1.1.1.1', + }); + + expect(filter).toHaveProperty('range'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter.range).toHaveProperty('ip'); + expect(filter.range.ip).toHaveProperty('gte', '0.0.0.0'); + expect(filter.range.ip).toHaveProperty('lte', '1.1.1.1'); + }); + + it('should return a range filter for ip_range agg using a CIDR mask', () => { + const aggConfigs = getAggConfigs([ + { + type: BUCKET_TYPES.IP_RANGE, + schema: 'segment', + params: { + field: 'ip', + ipRangeType: 'mask', + ranges: { + mask: [{ mask: '67.129.65.201/27' }], + }, + }, + }, + ]); + + const filter = createFilterIpRange(aggConfigs.aggs[0], { + type: 'mask', + mask: '67.129.65.201/27', + }); + + expect(filter).toHaveProperty('range'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter.range).toHaveProperty('ip'); + expect(filter.range.ip).toHaveProperty('gte', '67.129.65.192'); + expect(filter.range.ip).toHaveProperty('lte', '67.129.65.223'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts index 83769578725f29..803f6d97ae42d3 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts @@ -17,13 +17,13 @@ * under the License. */ -import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query'; import { CidrMask } from '../../../utils/cidr_mask'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { IpRangeKey } from '../ip_range'; +import { esFilters } from '../../../../../../plugins/data/public'; export const createFilterIpRange = (aggConfig: IBucketAggConfig, key: IpRangeKey) => { - let range: RangeFilterParams; + let range: esFilters.RangeFilterParams; if (key.type === 'mask') { range = new CidrMask(key.mask).getRange(); @@ -34,7 +34,7 @@ export const createFilterIpRange = (aggConfig: IBucketAggConfig, key: IpRangeKey }; } - return buildRangeFilter( + return esFilters.buildRangeFilter( aggConfig.params.field, { gte: range.from, lte: range.to }, aggConfig.getIndexPattern() diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts new file mode 100644 index 00000000000000..04476ba62ccd5f --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { createFilterRange } from './range'; +import { BytesFormat } from '../../../../../../plugins/data/common'; +import { AggConfigs } from '../../agg_configs'; +import { BUCKET_TYPES } from '../bucket_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('range', () => { + const getAggConfigs = () => { + const field = { + name: 'bytes', + format: new BytesFormat({}, () => {}), + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + id: BUCKET_TYPES.RANGE, + type: BUCKET_TYPES.RANGE, + schema: 'buckets', + params: { + field: 'bytes', + ranges: [{ from: 1024, to: 2048 }], + }, + }, + ], + null + ); + }; + + it('should return a range filter for range agg', () => { + const aggConfigs = getAggConfigs(); + const filter = createFilterRange(aggConfigs.aggs[0], { gte: 1024, lt: 2048.0 }); + + expect(filter).toHaveProperty('range'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter.range).toHaveProperty('bytes'); + expect(filter.range.bytes).toHaveProperty('gte', 1024.0); + expect(filter.range.bytes).toHaveProperty('lt', 2048.0); + expect(filter.meta).toHaveProperty('formattedValue', '≥ 1,024 and < 2,048'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/range.ts index cf2c83884651a1..929827c6e3fec6 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/range.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/range.ts @@ -17,11 +17,11 @@ * under the License. */ -import { buildRangeFilter } from '@kbn/es-query'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { esFilters } from '../../../../../../plugins/data/public'; export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => { - return buildRangeFilter( + return esFilters.buildRangeFilter( aggConfig.params.field, params, aggConfig.getIndexPattern(), diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts new file mode 100644 index 00000000000000..42f8349d5a2a3d --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts @@ -0,0 +1,118 @@ +/* + * 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 { createFilterTerms } from './terms'; +import { AggConfigs } from '../../agg_configs'; +import { BUCKET_TYPES } from '../bucket_agg_types'; +import { esFilters } from '../../../../../../plugins/data/public'; + +jest.mock('ui/new_platform'); + +describe('AggConfig Filters', () => { + describe('terms', () => { + const getAggConfigs = (aggs: Array>) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const field = { + name: 'field', + indexPattern, + }; + + return new AggConfigs(indexPattern, aggs, null); + }; + + it('should return a match_phrase filter for terms', () => { + const aggConfigs = getAggConfigs([ + { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, + ]); + + const filter = createFilterTerms(aggConfigs.aggs[0], 'apache', {}) as esFilters.Filter; + + expect(filter).toHaveProperty('query'); + expect(filter.query).toHaveProperty('match_phrase'); + expect(filter.query.match_phrase).toHaveProperty('field'); + expect(filter.query.match_phrase.field).toBe('apache'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + }); + + it('should set query to true or false for boolean filter', () => { + const aggConfigs = getAggConfigs([ + { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, + ]); + + const filterFalse = createFilterTerms(aggConfigs.aggs[0], '', {}) as esFilters.Filter; + + expect(filterFalse).toHaveProperty('query'); + expect(filterFalse.query).toHaveProperty('match_phrase'); + expect(filterFalse.query.match_phrase).toHaveProperty('field'); + expect(filterFalse.query.match_phrase.field).toBeFalsy(); + + const filterTrue = createFilterTerms(aggConfigs.aggs[0], '1', {}) as esFilters.Filter; + + expect(filterTrue).toHaveProperty('query'); + expect(filterTrue.query).toHaveProperty('match_phrase'); + expect(filterTrue.query.match_phrase).toHaveProperty('field'); + expect(filterTrue.query.match_phrase.field).toBeTruthy(); + }); + // + it('should generate correct __missing__ filter', () => { + const aggConfigs = getAggConfigs([ + { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, + ]); + const filter = createFilterTerms( + aggConfigs.aggs[0], + '__missing__', + {} + ) as esFilters.ExistsFilter; + + expect(filter).toHaveProperty('exists'); + expect(filter.exists).toHaveProperty('field', 'field'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter.meta).toHaveProperty('negate', true); + }); + // + it('should generate correct __other__ filter', () => { + const aggConfigs = getAggConfigs([ + { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, + ]); + + const [filter] = createFilterTerms(aggConfigs.aggs[0], '__other__', { + terms: ['apache'], + }) as esFilters.Filter[]; + + expect(filter).toHaveProperty('query'); + expect(filter.query).toHaveProperty('bool'); + expect(filter.query.bool).toHaveProperty('should'); + expect(filter.query.bool.should[0]).toHaveProperty('match_phrase'); + expect(filter.query.bool.should[0].match_phrase).toHaveProperty('field', 'apache'); + expect(filter).toHaveProperty('meta'); + expect(filter.meta).toHaveProperty('index', '1234'); + expect(filter.meta).toHaveProperty('negate', true); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts index e5d4406c752c7c..5bd770e672786d 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Filter, buildPhraseFilter, buildPhrasesFilter, buildExistsFilter } from '@kbn/es-query'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { esFilters } from '../../../../../../plugins/data/public'; export const createFilterTerms = (aggConfig: IBucketAggConfig, key: string, params: any) => { const field = aggConfig.params.field; @@ -27,20 +27,20 @@ export const createFilterTerms = (aggConfig: IBucketAggConfig, key: string, para if (key === '__other__') { const terms = params.terms; - const phraseFilter = buildPhrasesFilter(field, terms, indexPattern); + const phraseFilter = esFilters.buildPhrasesFilter(field, terms, indexPattern); phraseFilter.meta.negate = true; - const filters: Filter[] = [phraseFilter]; + const filters: esFilters.Filter[] = [phraseFilter]; if (terms.some((term: string) => term === '__missing__')) { - filters.push(buildExistsFilter(field, indexPattern)); + filters.push(esFilters.buildExistsFilter(field, indexPattern)); } return filters; } else if (key === '__missing__') { - const existsFilter = buildExistsFilter(field, indexPattern); + const existsFilter = esFilters.buildExistsFilter(field, indexPattern); existsFilter.meta.negate = true; return existsFilter; } - return buildPhraseFilter(field, key, indexPattern); + return esFilters.buildPhraseFilter(field, key, indexPattern); }; diff --git a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts index 4c2e67f758a7e2..e86d561a1c79bd 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts @@ -32,7 +32,6 @@ import { ScaleMetricsParamEditor } from '../../vis/editors/default/controls/scal import { dateHistogramInterval } from '../../../../core_plugins/data/public'; import { writeParams } from '../agg_params'; import { AggConfigs } from '../agg_configs'; -import { AggConfig } from '../agg_config'; import { isMetricAggType } from '../metrics/metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; @@ -189,7 +188,7 @@ export const dateHistogramBucketAgg = new BucketAggType isMetricAggType(a.type)); - const all = _.every(metrics, (a: AggConfig) => { + const all = _.every(metrics, (a: IBucketAggConfig) => { const { type } = a; if (isMetricAggType(type)) { diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.test.ts b/src/legacy/ui/public/agg_types/buckets/date_range.test.ts new file mode 100644 index 00000000000000..7d9fe002636a21 --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/date_range.test.ts @@ -0,0 +1,112 @@ +/* + * 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 { AggConfigs } from '../agg_configs'; +import { BUCKET_TYPES } from './bucket_agg_types'; +import { npStart } from 'ui/new_platform'; + +jest.mock('ui/new_platform'); + +describe('date_range params', () => { + const getAggConfigs = (params: Record = {}, hasIncludeTypeMeta: boolean = true) => { + const field = { + name: 'bytes', + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + typeMeta: hasIncludeTypeMeta + ? { + aggs: { + date_range: { + bytes: { + time_zone: 'defaultTimeZone', + }, + }, + }, + } + : undefined, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + id: BUCKET_TYPES.DATE_RANGE, + type: BUCKET_TYPES.DATE_RANGE, + schema: 'buckets', + params, + }, + ], + null + ); + }; + + describe('getKey', () => { + it('should return object', () => { + const aggConfigs = getAggConfigs(); + const dateRange = aggConfigs.aggs[0]; + const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' }; + + expect(dateRange.getKey(bucket)).toEqual({ from: 'from-date', to: 'to-date' }); + }); + }); + + describe('time_zone', () => { + it('should use the specified time_zone', () => { + const aggConfigs = getAggConfigs({ + time_zone: 'Europe/Minsk', + field: 'bytes', + }); + const dateRange = aggConfigs.aggs[0]; + const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; + + expect(params.time_zone).toBe('Europe/Minsk'); + }); + + it('should use the fixed time_zone from the index pattern typeMeta', () => { + const aggConfigs = getAggConfigs({ + field: 'bytes', + }); + const dateRange = aggConfigs.aggs[0]; + const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; + + expect(params.time_zone).toBe('defaultTimeZone'); + }); + + it('should use the Kibana time_zone if no parameter specified', () => { + npStart.core.uiSettings.get = jest.fn(() => 'kibanaTimeZone'); + + const aggConfigs = getAggConfigs( + { + field: 'bytes', + }, + false + ); + const dateRange = aggConfigs.aggs[0]; + const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; + + expect(params.time_zone).toBe('kibanaTimeZone'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.ts b/src/legacy/ui/public/agg_types/buckets/date_range.ts index dd7f0cb972ae22..4de6002e2e3746 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_range.ts @@ -21,9 +21,8 @@ import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; -import { AggConfig } from '../agg_config'; import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; import { DateRangesParamEditor } from '../../vis/editors/default/controls/date_ranges'; @@ -64,7 +63,7 @@ export const dateRangeBucketAgg = new BucketAggType({ name: 'field', type: 'field', filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: AggConfig) { + default(agg: IBucketAggConfig) { return agg.getIndexPattern().timeFieldName; }, }, @@ -83,7 +82,7 @@ export const dateRangeBucketAgg = new BucketAggType({ default: undefined, // Implimentation method is the same as that of date_histogram serialize: () => undefined, - write: (agg: AggConfig, output: Record) => { + write: (agg: IBucketAggConfig, output: Record) => { const field = agg.getParam('field'); let tz = agg.getParam('time_zone'); diff --git a/src/legacy/ui/public/agg_types/buckets/filters.ts b/src/legacy/ui/public/agg_types/buckets/filters.ts index f0450f220f610f..44a97abb7a1d77 100644 --- a/src/legacy/ui/public/agg_types/buckets/filters.ts +++ b/src/legacy/ui/public/agg_types/buckets/filters.ts @@ -21,7 +21,6 @@ import _ from 'lodash'; import angular from 'angular'; import { i18n } from '@kbn/i18n'; -import { Storage } from 'ui/storage'; import chrome from 'ui/chrome'; import { buildEsQuery } from '@kbn/es-query'; @@ -29,6 +28,7 @@ import { FiltersParamEditor, FilterValue } from '../../vis/editors/default/contr import { createFilterFilters } from './create_filter/filters'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { setup as data } from '../../../../core_plugins/data/public/legacy'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; const { getQueryLog } = data.query.helpers; const config = chrome.getUiSettingsClient(); diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts new file mode 100644 index 00000000000000..5c599f16e09c2e --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts @@ -0,0 +1,216 @@ +/* + * 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 { geoHashBucketAgg, IBucketGeoHashGridAggConfig } from './geo_hash'; +import { AggConfigs } from '../agg_configs'; +import { BUCKET_TYPES } from './bucket_agg_types'; + +jest.mock('ui/new_platform'); + +describe('Geohash Agg', () => { + const getAggConfigs = (params?: Record) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const field = { + name: 'location', + indexPattern, + }; + + return new AggConfigs( + indexPattern, + [ + { + id: BUCKET_TYPES.GEOHASH_GRID, + type: BUCKET_TYPES.GEOHASH_GRID, + schema: 'segment', + params: { + field: { + name: 'location', + }, + isFilteredByCollar: true, + useGeocentroid: true, + mapZoom: 10, + mapBounds: { + top_left: { lat: 1.0, lon: -1.0 }, + bottom_right: { lat: -1.0, lon: 1.0 }, + }, + ...params, + }, + }, + ], + null + ); + }; + + describe('precision parameter', () => { + const PRECISION_PARAM_INDEX = 2; + + let precisionParam: any; + + beforeEach(() => { + precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX]; + }); + + it('should select precision parameter', () => { + expect(precisionParam.name).toEqual('precision'); + }); + + describe('precision parameter write', () => { + const zoomToGeoHashPrecision: Record = { + 0: 1, + 1: 2, + 2: 2, + 3: 2, + 4: 3, + 5: 3, + 6: 4, + 7: 4, + 8: 4, + 9: 5, + 10: 5, + 11: 6, + 12: 6, + 13: 6, + 14: 7, + 15: 7, + 16: 8, + 17: 8, + 18: 8, + 19: 9, + 20: 9, + 21: 10, + }; + + Object.keys(zoomToGeoHashPrecision).forEach((zoomLevel: string) => { + it(`zoom level ${zoomLevel} should correspond to correct geohash-precision`, () => { + const aggConfigs = getAggConfigs({ + autoPrecision: true, + mapZoom: zoomLevel, + }); + + const { [BUCKET_TYPES.GEOHASH_GRID]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.precision).toEqual(zoomToGeoHashPrecision[zoomLevel]); + }); + }); + }); + }); + + describe('getRequestAggs', () => { + describe('initial aggregation creation', () => { + let aggConfigs: AggConfigs; + let geoHashGridAgg: IBucketGeoHashGridAggConfig; + + beforeEach(() => { + aggConfigs = getAggConfigs(); + geoHashGridAgg = aggConfigs.aggs[0] as IBucketGeoHashGridAggConfig; + }); + + it('should create filter, geohash_grid, and geo_centroid aggregations', () => { + const requestAggs = geoHashBucketAgg.getRequestAggs( + geoHashGridAgg + ) as IBucketGeoHashGridAggConfig[]; + + expect(requestAggs.length).toEqual(3); + expect(requestAggs[0].type.name).toEqual('filter'); + expect(requestAggs[1].type.name).toEqual('geohash_grid'); + expect(requestAggs[2].type.name).toEqual('geo_centroid'); + }); + + it('should set mapCollar in vis session state', () => { + const [, geoHashAgg] = geoHashBucketAgg.getRequestAggs( + geoHashGridAgg + ) as IBucketGeoHashGridAggConfig[]; + + expect(geoHashAgg).toHaveProperty('lastMapCollar'); + expect(geoHashAgg.lastMapCollar).toHaveProperty('top_left'); + expect(geoHashAgg.lastMapCollar).toHaveProperty('bottom_right'); + expect(geoHashAgg.lastMapCollar).toHaveProperty('zoom'); + }); + }); + }); + + describe('aggregation options', () => { + it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { + const aggConfigs = getAggConfigs({ isFilteredByCollar: false }); + const requestAggs = geoHashBucketAgg.getRequestAggs(aggConfigs + .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[]; + + expect(requestAggs.length).toEqual(2); + expect(requestAggs[0].type.name).toEqual('geohash_grid'); + expect(requestAggs[1].type.name).toEqual('geo_centroid'); + }); + + it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { + const aggConfigs = getAggConfigs({ useGeocentroid: false }); + const requestAggs = geoHashBucketAgg.getRequestAggs(aggConfigs + .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[]; + + expect(requestAggs.length).toEqual(2); + expect(requestAggs[0].type.name).toEqual('filter'); + expect(requestAggs[1].type.name).toEqual('geohash_grid'); + }); + }); + + describe('aggregation creation after map interaction', () => { + let originalRequestAggs: IBucketGeoHashGridAggConfig[]; + + beforeEach(() => { + originalRequestAggs = geoHashBucketAgg.getRequestAggs(getAggConfigs() + .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[]; + }); + + it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { + const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({ + mapBounds: { + top_left: { lat: 10.0, lon: -10.0 }, + bottom_right: { lat: 9.0, lon: -9.0 }, + }, + }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[]; + + expect(originalRequestAggs[1].params).not.toEqual(geoBoxingBox.params); + }); + + it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { + const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({ + mapBounds: { + top_left: { lat: 1, lon: -1 }, + bottom_right: { lat: -1, lon: 1 }, + }, + }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[]; + + expect(originalRequestAggs[1].params).toEqual(geoBoxingBox.params); + }); + + it('should change geo_bounding_box filter aggregation and vis session state when map zoom level changes', () => { + const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({ + mapZoom: -1, + }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[]; + + expect(originalRequestAggs[1].lastMapCollar).not.toEqual(geoBoxingBox.lastMapCollar); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts index 555aa94b636b84..1716891231b838 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts @@ -26,7 +26,6 @@ import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/control import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision'; import { geohashColumns } from '../../utils/decode_geo_hash'; import { AggGroupNames } from '../../vis/editors/default/agg_groups'; -import { AggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; // @ts-ignore @@ -143,7 +142,7 @@ export const geoHashBucketAgg = new BucketAggType({ }, ], getRequestAggs(agg) { - const aggs: AggConfig[] = []; + const aggs: IBucketAggConfig[] = []; const params = agg.params; if (params.isFilteredByCollar && agg.getField()) { diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/histogram.test.ts new file mode 100644 index 00000000000000..338af2e41cb88f --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/histogram.test.ts @@ -0,0 +1,292 @@ +/* + * 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 { npStart } from 'ui/new_platform'; +import { AggConfigs } from '../index'; +import { BUCKET_TYPES } from './bucket_agg_types'; +import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram'; +import { BucketAggType } from './_bucket_agg_type'; + +jest.mock('ui/new_platform'); + +describe('Histogram Agg', () => { + const getAggConfigs = (params: Record = {}) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const field = { + name: 'field', + indexPattern, + }; + + return new AggConfigs( + indexPattern, + [ + { + field: { + name: 'field', + }, + id: 'test', + type: BUCKET_TYPES.HISTOGRAM, + schema: 'segment', + params, + }, + ], + null + ); + }; + + const getParams = (options: Record) => { + const aggConfigs = getAggConfigs({ + ...options, + field: { + name: 'field', + }, + }); + return aggConfigs.aggs[0].toDsl()[BUCKET_TYPES.HISTOGRAM]; + }; + + describe('ordered', () => { + let histogramType: BucketAggType; + + beforeEach(() => { + histogramType = histogramBucketAgg; + }); + + it('is ordered', () => { + expect(histogramType.ordered).toBeDefined(); + }); + + it('is not ordered by date', () => { + expect(histogramType.ordered).not.toHaveProperty('date'); + }); + }); + + describe('params', () => { + describe('intervalBase', () => { + it('should not be written to the DSL', () => { + const aggConfigs = getAggConfigs({ + intervalBase: 100, + field: { + name: 'field', + }, + }); + const { [BUCKET_TYPES.HISTOGRAM]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params).not.toHaveProperty('intervalBase'); + }); + }); + + describe('interval', () => { + it('accepts a whole number', () => { + const params = getParams({ + interval: 100, + }); + + expect(params).toHaveProperty('interval', 100); + }); + + it('accepts a decimal number', function() { + const params = getParams({ + interval: 0.1, + }); + + expect(params).toHaveProperty('interval', 0.1); + }); + + it('accepts a decimal number string', function() { + const params = getParams({ + interval: '0.1', + }); + + expect(params).toHaveProperty('interval', 0.1); + }); + + it('accepts a whole number string', function() { + const params = getParams({ + interval: '10', + }); + + expect(params).toHaveProperty('interval', 10); + }); + + it('fails on non-numeric values', function() { + const params = getParams({ + interval: [], + }); + + expect(params.interval).toBeNaN(); + }); + + describe('interval scaling', () => { + const getInterval = ( + maxBars: number, + params?: Record, + autoBounds?: AutoBounds + ) => { + const aggConfigs = getAggConfigs({ + ...params, + field: { + name: 'field', + }, + }); + const aggConfig = aggConfigs.aggs[0] as IBucketHistogramAggConfig; + + if (autoBounds) { + aggConfig.setAutoBounds(autoBounds); + } + + // mock histogram:maxBars value; + npStart.core.uiSettings.get = jest.fn(() => maxBars); + + return aggConfig.write(aggConfigs).params; + }; + + it('will respect the histogram:maxBars setting', () => { + const params = getInterval( + 5, + { interval: 5 }, + { + min: 0, + max: 10000, + } + ); + + expect(params).toHaveProperty('interval', 2000); + }); + + it('will return specified interval, if bars are below histogram:maxBars config', () => { + const params = getInterval(100, { interval: 5 }); + + expect(params).toHaveProperty('interval', 5); + }); + + it('will set to intervalBase if interval is below base', () => { + const params = getInterval(1000, { interval: 3, intervalBase: 8 }); + + expect(params).toHaveProperty('interval', 8); + }); + + it('will round to nearest intervalBase multiple if interval is above base', () => { + const roundUp = getInterval(1000, { interval: 46, intervalBase: 10 }); + expect(roundUp).toHaveProperty('interval', 50); + + const roundDown = getInterval(1000, { interval: 43, intervalBase: 10 }); + expect(roundDown).toHaveProperty('interval', 40); + }); + + it('will not change interval if it is a multiple of base', () => { + const output = getInterval(1000, { interval: 35, intervalBase: 5 }); + + expect(output).toHaveProperty('interval', 35); + }); + + it('will round to intervalBase after scaling histogram:maxBars', () => { + const output = getInterval(100, { interval: 5, intervalBase: 6 }, { min: 0, max: 1000 }); + + // 100 buckets in 0 to 1000 would result in an interval of 10, so we should + // round to the next multiple of 6 -> 12 + expect(output).toHaveProperty('interval', 12); + }); + }); + + describe('min_doc_count', () => { + let output: Record; + + it('casts true values to 0', () => { + output = getParams({ min_doc_count: true }); + expect(output).toHaveProperty('min_doc_count', 0); + + output = getParams({ min_doc_count: 'yes' }); + expect(output).toHaveProperty('min_doc_count', 0); + + output = getParams({ min_doc_count: 1 }); + expect(output).toHaveProperty('min_doc_count', 0); + + output = getParams({ min_doc_count: {} }); + expect(output).toHaveProperty('min_doc_count', 0); + }); + + it('writes 1 for falsy values', () => { + output = getParams({ min_doc_count: '' }); + expect(output).toHaveProperty('min_doc_count', 1); + + output = getParams({ min_doc_count: null }); + expect(output).toHaveProperty('min_doc_count', 1); + + output = getParams({ min_doc_count: undefined }); + expect(output).toHaveProperty('min_doc_count', 1); + }); + }); + + describe('extended_bounds', function() { + it('does not write when only eb.min is set', function() { + const output = getParams({ + has_extended_bounds: true, + extended_bounds: { min: 0 }, + }); + expect(output).not.toHaveProperty('extended_bounds'); + }); + + it('does not write when only eb.max is set', function() { + const output = getParams({ + has_extended_bounds: true, + extended_bounds: { max: 0 }, + }); + + expect(output).not.toHaveProperty('extended_bounds'); + }); + + it('writes when both eb.min and eb.max are set', function() { + const output = getParams({ + has_extended_bounds: true, + extended_bounds: { min: 99, max: 100 }, + }); + + expect(output.extended_bounds).toHaveProperty('min', 99); + expect(output.extended_bounds).toHaveProperty('max', 100); + }); + + it('does not write when nothing is set', function() { + const output = getParams({ + has_extended_bounds: true, + extended_bounds: {}, + }); + + expect(output).not.toHaveProperty('extended_bounds'); + }); + + it('does not write when has_extended_bounds is false', function() { + const output = getParams({ + has_extended_bounds: false, + extended_bounds: { min: 99, max: 100 }, + }); + + expect(output).not.toHaveProperty('extended_bounds'); + }); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.ts b/src/legacy/ui/public/agg_types/buckets/histogram.ts index 23edefc67d506c..fba2f47010c34e 100644 --- a/src/legacy/ui/public/agg_types/buckets/histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/histogram.ts @@ -21,18 +21,17 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; -import chrome from '../../chrome'; +import { npStart } from 'ui/new_platform'; import { BucketAggType, IBucketAggConfig, BucketAggParam } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { NumberIntervalParamEditor } from '../../vis/editors/default/controls/number_interval'; import { MinDocCountParamEditor } from '../../vis/editors/default/controls/min_doc_count'; import { HasExtendedBoundsParamEditor } from '../../vis/editors/default/controls/has_extended_bounds'; import { ExtendedBoundsParamEditor } from '../../vis/editors/default/controls/extended_bounds'; -import { AggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; import { BUCKET_TYPES } from './bucket_agg_types'; -interface AutoBounds { +export interface AutoBounds { min: number; max: number; } @@ -42,7 +41,8 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { getAutoBounds: () => AutoBounds; } -const config = chrome.getUiSettingsClient(); +const getUIConfig = () => npStart.core.uiSettings; + export const histogramBucketAgg = new BucketAggType({ name: BUCKET_TYPES.HISTOGRAM, title: i18n.translate('common.ui.aggTypes.buckets.histogramTitle', { @@ -135,25 +135,30 @@ export const histogramBucketAgg = new BucketAggType({ if (interval <= 0) { interval = 1; } + const autoBounds = aggConfig.getAutoBounds(); // ensure interval does not create too many buckets and crash browser - if (aggConfig.getAutoBounds()) { - const range = aggConfig.getAutoBounds().max - aggConfig.getAutoBounds().min; + if (autoBounds) { + const range = autoBounds.max - autoBounds.min; const bars = range / interval; + + const config = getUIConfig(); if (bars > config.get('histogram:maxBars')) { const minInterval = range / config.get('histogram:maxBars'); + // Round interval by order of magnitude to provide clean intervals // Always round interval up so there will always be less buckets than histogram:maxBars const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); let roundInterval = orderOfMagnitude; + while (roundInterval < minInterval) { roundInterval += orderOfMagnitude; } interval = roundInterval; } } - const base = aggConfig.params.intervalBase; + if (base) { if (interval < base) { // In case the specified interval is below the base, just increase it to it's base @@ -171,7 +176,7 @@ export const histogramBucketAgg = new BucketAggType({ name: 'min_doc_count', default: false, editorComponent: MinDocCountParamEditor, - write(aggConfig: AggConfig, output: Record) { + write(aggConfig: IBucketAggConfig, output: Record) { if (aggConfig.params.min_doc_count) { output.params.min_doc_count = 0; } else { @@ -192,14 +197,14 @@ export const histogramBucketAgg = new BucketAggType({ max: '', }, editorComponent: ExtendedBoundsParamEditor, - write(aggConfig: AggConfig, output: Record) { + write(aggConfig: IBucketAggConfig, output: Record) { const { min, max } = aggConfig.params.extended_bounds; if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { output.params.extended_bounds = { min, max }; } }, - shouldShow: (aggConfig: AggConfig) => aggConfig.params.has_extended_bounds, + shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, }, ], }); diff --git a/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts b/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts index 2bf0930d376845..e4527ff87f48cc 100644 --- a/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts +++ b/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts @@ -18,7 +18,6 @@ */ import { isString, isObject } from 'lodash'; -import { AggConfig } from 'ui/agg_types'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; export const isType = (type: string) => { @@ -32,12 +31,16 @@ export const isType = (type: string) => { export const isStringType = isType('string'); export const migrateIncludeExcludeFormat = { - serialize(this: BucketAggParam, value: any, agg: AggConfig) { + serialize(this: BucketAggParam, value: any, agg: IBucketAggConfig) { if (this.shouldShow && !this.shouldShow(agg)) return; if (!value || isString(value)) return value; else return value.pattern; }, - write(this: BucketAggType, aggConfig: AggConfig, output: Record) { + write( + this: BucketAggType, + aggConfig: IBucketAggConfig, + output: Record + ) { const value = aggConfig.getParam(this.name); if (isObject(value)) { diff --git a/src/legacy/ui/public/agg_types/buckets/range.test.ts b/src/legacy/ui/public/agg_types/buckets/range.test.ts new file mode 100644 index 00000000000000..f7cae60cce773b --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/range.test.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 { AggConfigs } from '../agg_configs'; +import { BUCKET_TYPES } from './bucket_agg_types'; +import { NumberFormat } from '../../../../../plugins/data/common/'; + +jest.mock('ui/new_platform'); + +const buckets = [ + { + to: 1024, + to_as_string: '1024.0', + doc_count: 20904, + }, + { + from: 1024, + from_as_string: '1024.0', + to: 2560, + to_as_string: '2560.0', + doc_count: 23358, + }, + { + from: 2560, + from_as_string: '2560.0', + doc_count: 174250, + }, +]; + +describe('Range Agg', () => { + const getAggConfigs = () => { + const field = { + name: 'bytes', + format: new NumberFormat( + { + pattern: '0,0.[000] b', + }, + () => {} + ), + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + type: BUCKET_TYPES.RANGE, + schema: 'segment', + params: { + field: 'bytes', + ranges: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }], + }, + }, + ], + null + ); + }; + + describe('formating', () => { + it('formats bucket keys properly', () => { + const aggConfigs = getAggConfigs(); + const agg = aggConfigs.aggs[0]; + + const format = (val: any) => agg.fieldFormatter()(agg.getKey(val)); + + expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB'); + expect(format(buckets[1])).toBe('≥ 1 KB and < 2.5 KB'); + expect(format(buckets[2])).toBe('≥ 2.5 KB and < +∞'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts b/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts new file mode 100644 index 00000000000000..454f1bf70a790b --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts @@ -0,0 +1,110 @@ +/* + * 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 { AggConfigs } from '../index'; +import { BUCKET_TYPES } from './bucket_agg_types'; +import { significantTermsBucketAgg } from './significant_terms'; + +jest.mock('ui/new_platform'); + +describe('Significant Terms Agg', () => { + describe('order agg editor UI', () => { + describe('convert include/exclude from old format', () => { + const getAggConfigs = (params: Record = {}) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const field = { + name: 'field', + indexPattern, + }; + + return new AggConfigs( + indexPattern, + [ + { + id: 'test', + type: BUCKET_TYPES.SIGNIFICANT_TERMS, + schema: 'segment', + params, + }, + ], + null + ); + }; + + const testSerializeAndWrite = (aggs: AggConfigs) => { + const agg = aggs.aggs[0]; + const { [BUCKET_TYPES.SIGNIFICANT_TERMS]: params } = agg.toDsl(); + + expect(params.field).toBe('field'); + expect(params.include).toBe('404'); + expect(params.exclude).toBe('400'); + }; + + it('should generate correct label', () => { + const aggConfigs = getAggConfigs({ + size: 'SIZE', + field: { + name: 'FIELD', + }, + }); + const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0]); + + expect(label).toBe('Top SIZE unusual terms in FIELD'); + }); + + it('should doesnt do anything with string type', () => { + const aggConfigs = getAggConfigs({ + include: '404', + exclude: '400', + field: { + name: 'field', + type: 'string', + }, + }); + + testSerializeAndWrite(aggConfigs); + }); + + it('should converts object to string type', () => { + const aggConfigs = getAggConfigs({ + include: { + pattern: '404', + }, + exclude: { + pattern: '400', + }, + field: { + name: 'field', + type: 'string', + }, + }); + + testSerializeAndWrite(aggConfigs); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/buckets/terms.test.ts b/src/legacy/ui/public/agg_types/buckets/terms.test.ts new file mode 100644 index 00000000000000..24ac332ae4d55c --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/terms.test.ts @@ -0,0 +1,78 @@ +/* + * 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 { AggConfigs } from '../index'; +import { BUCKET_TYPES } from './bucket_agg_types'; + +jest.mock('ui/new_platform'); + +describe('Terms Agg', () => { + describe('order agg editor UI', () => { + const getAggConfigs = (params: Record = {}) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const field = { + name: 'field', + indexPattern, + }; + + return new AggConfigs( + indexPattern, + [ + { + id: 'test', + params, + type: BUCKET_TYPES.TERMS, + }, + ], + null + ); + }; + + it('converts object to string type', function() { + const aggConfigs = getAggConfigs({ + include: { + pattern: '404', + }, + exclude: { + pattern: '400', + }, + field: { + name: 'field', + }, + orderAgg: { + type: 'count', + }, + }); + + const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + + expect(params.field).toBe('field'); + expect(params.include).toBe('404'); + expect(params.exclude).toBe('400'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts b/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts index 0d32c9dc769da2..431e1161e0dbdf 100644 --- a/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts +++ b/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts @@ -27,7 +27,7 @@ describe('prop filter', () => { nameFilter = propFilter('name'); }); - function getObjects(...names: string[]) { + const getObjects = (...names: string[]) => { const count = new Map(); const objects = []; @@ -41,8 +41,9 @@ describe('prop filter', () => { }); count.set(name, count.get(name) + 1); } + return objects; - } + }; it('returns list when no filters are provided', () => { const objects = getObjects('table', 'table', 'pie'); diff --git a/src/legacy/ui/public/agg_types/metrics/get_response_agg_config_class.ts b/src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts similarity index 75% rename from src/legacy/ui/public/agg_types/metrics/get_response_agg_config_class.ts rename to src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts index 34658431a96abe..054543de3dd067 100644 --- a/src/legacy/ui/public/agg_types/metrics/get_response_agg_config_class.ts +++ b/src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts @@ -16,9 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - import { assign } from 'lodash'; -import { IMetricAggConfig } from './metric_agg_type'; +import { IMetricAggConfig } from '../metric_agg_type'; /** * Get the ResponseAggConfig class for an aggConfig, @@ -41,10 +40,7 @@ export interface IResponseAggConfig extends IMetricAggConfig { parentId: IMetricAggConfig['id']; } -export const create = ( - parentAgg: IMetricAggConfig, - props: Partial -): IMetricAggConfig => { +export const create = (parentAgg: IMetricAggConfig, props: Partial) => { /** * AggConfig "wrapper" for multi-value metric aggs which * need to modify AggConfig behavior for each value produced. @@ -52,23 +48,21 @@ export const create = ( * @param {string|number} key - the key or index that identifies * this part of the multi-value */ - class ResponseAggConfig { - id: IMetricAggConfig['id']; - key: string | number; - parentId: IMetricAggConfig['id']; - - constructor(key: string) { - this.key = key; - this.parentId = parentAgg.id; + function ResponseAggConfig(this: IResponseAggConfig, key: string) { + const parentId = parentAgg.id; + let id; - const subId = String(key); + const subId = String(key); - if (subId.indexOf('.') > -1) { - this.id = this.parentId + "['" + subId.replace(/'/g, "\\'") + "']"; - } else { - this.id = this.parentId + '.' + subId; - } + if (subId.indexOf('.') > -1) { + id = parentId + "['" + subId.replace(/'/g, "\\'") + "']"; + } else { + id = parentId + '.' + subId; } + + this.id = id; + this.key = key; + this.parentId = parentId; } ResponseAggConfig.prototype = Object.create(parentAgg); @@ -76,5 +70,5 @@ export const create = ( assign(ResponseAggConfig.prototype, props); - return (ResponseAggConfig as unknown) as IMetricAggConfig; + return ResponseAggConfig; }; diff --git a/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts b/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts new file mode 100644 index 00000000000000..479ff40b7c0aec --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts @@ -0,0 +1,59 @@ +/* + * 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 { makeNestedLabel } from './make_nested_label'; +import { IMetricAggConfig } from '../metric_agg_type'; + +describe('metric agg make_nested_label', () => { + const generateAggConfig = (metricLabel: string): IMetricAggConfig => { + return ({ + params: { + customMetric: { + makeLabel: () => { + return metricLabel; + }, + }, + }, + getParam(this: IMetricAggConfig, key: string) { + return this.params[key]; + }, + } as unknown) as IMetricAggConfig; + }; + + it('should return a metric label with prefix', () => { + const aggConfig = generateAggConfig('Count'); + const label = makeNestedLabel(aggConfig, 'derivative'); + + expect(label).toEqual('Derivative of Count'); + }); + + it('should return a numbered prefix', () => { + const aggConfig = generateAggConfig('Derivative of Count'); + const label = makeNestedLabel(aggConfig, 'derivative'); + + expect(label).toEqual('2. derivative of Count'); + }); + + it('should return a prefix with correct order', () => { + const aggConfig = generateAggConfig('3. derivative of Count'); + const label = makeNestedLabel(aggConfig, 'derivative'); + + expect(label).toEqual('4. derivative of Count'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/median.test.ts b/src/legacy/ui/public/agg_types/metrics/median.test.ts new file mode 100644 index 00000000000000..819c24f135cdc1 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/median.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypeMetricMedianProvider class', () => { + let aggConfigs: AggConfigs; + + beforeEach(() => { + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.MEDIAN, + type: METRIC_TYPES.MEDIAN, + schema: 'metric', + params: { + field: 'bytes', + percents: [70], + }, + }, + ], + null + ); + }); + + it('requests the percentiles aggregation in the Elasticsearch query DSL', () => { + const dsl: Record = aggConfigs.toDsl(); + + expect(dsl.median.percentiles.percents).toEqual([70]); + }); + + it('asks Elasticsearch for array-based values in the aggregation response', () => { + const dsl: Record = aggConfigs.toDsl(); + + expect(dsl.median.percentiles.keyed).toBeFalsy(); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts new file mode 100644 index 00000000000000..7c7a2a68cd7c55 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts @@ -0,0 +1,248 @@ +/* + * 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 sinon from 'sinon'; +import { derivativeMetricAgg } from './derivative'; +import { cumulativeSumMetricAgg } from './cumulative_sum'; +import { movingAvgMetricAgg } from './moving_avg'; +import { serialDiffMetricAgg } from './serial_diff'; +import { AggConfigs } from '../agg_configs'; +import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; + +jest.mock('../../vis/editors/default/schemas', () => { + class MockedSchemas { + all = [{}]; + } + return { + Schemas: jest.fn().mockImplementation(() => new MockedSchemas()), + }; +}); + +jest.mock('../../vis/editors/default/controls/sub_metric', () => { + return { + SubMetricParamEditor() {}, + }; +}); + +jest.mock('../../vis/editors/default/controls/sub_agg', () => { + return { + SubAggParamEditor() {}, + }; +}); + +jest.mock('ui/new_platform'); + +describe('parent pipeline aggs', function() { + const metrics = [ + { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, + { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, + { name: 'moving_avg', title: 'Moving Avg', provider: movingAvgMetricAgg, dslName: 'moving_fn' }, + { name: 'serial_diff', title: 'Serial Diff', provider: serialDiffMetricAgg }, + ]; + + metrics.forEach(metric => { + describe(`${metric.title} metric`, () => { + let aggDsl: Record; + let metricAgg: MetricAggType; + let aggConfig: IMetricAggConfig; + + const init = ( + params: any = { + metricAgg: '1', + customMetric: null, + } + ) => { + const field = { + name: 'field', + format: { + type: { + id: 'bytes', + }, + }, + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: '1', + type: 'count', + schema: 'metric', + }, + { + id: '2', + type: metric.name, + schema: 'metric', + params, + }, + { + id: '3', + type: 'max', + params: { field: 'field' }, + schema: 'metric', + }, + ], + null + ); + + // Grab the aggConfig off the vis (we don't actually use the vis for anything else) + metricAgg = metric.provider; + aggConfig = aggConfigs.aggs[1]; + aggDsl = aggConfig.toDsl(aggConfigs); + }; + + it(`should return a label prefixed with ${metric.title} of`, () => { + init(); + + expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Count`); + }); + + it(`should return a label ${metric.title} of max bytes`, () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '1-orderAgg', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }); + expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Max field`); + }); + + it(`should return a label prefixed with number of ${metric.title.toLowerCase()}`, () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-orderAgg', + type: metric.name, + params: { + buckets_path: 'custom', + customMetric: { + id: '2-orderAgg-orderAgg', + type: 'count', + schema: 'orderAgg', + }, + }, + schema: 'orderAgg', + }, + }); + expect(metricAgg.makeLabel(aggConfig)).toEqual(`2. ${metric.title.toLowerCase()} of Count`); + }); + + it('should set parent aggs', () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }); + expect(aggDsl[metric.dslName || metric.name].buckets_path).toBe('2-metric'); + expect(aggDsl.parentAggs['2-metric'].max.field).toBe('field'); + }); + + it('should set nested parent aggs', () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: metric.name, + params: { + buckets_path: 'custom', + customMetric: { + id: '2-metric-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }, + schema: 'orderAgg', + }, + }); + expect(aggDsl[metric.dslName || metric.name].buckets_path).toBe('2-metric'); + expect(aggDsl.parentAggs['2-metric'][metric.dslName || metric.name].buckets_path).toBe( + '2-metric-metric' + ); + }); + + it('should have correct formatter', () => { + init({ + metricAgg: '3', + }); + expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + }); + + it('should have correct customMetric nested formatter', () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: metric.name, + params: { + buckets_path: 'custom', + customMetric: { + id: '2-metric-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }, + schema: 'orderAgg', + }, + }); + expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + }); + + it("should call modifyAggConfigOnSearchRequestStart for its customMetric's parameters", () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }); + + const searchSource: any = {}; + const customMetricSpy = sinon.spy(); + const customMetric = aggConfig.params.customMetric; + + // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter + customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; + + aggConfig.type.params.forEach(param => { + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + }); + expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts new file mode 100644 index 00000000000000..f3882ca57161f1 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; +import { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypesMetricsPercentileRanksProvider class', function() { + let aggConfigs: AggConfigs; + + beforeEach(() => { + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.PERCENTILE_RANKS, + type: METRIC_TYPES.PERCENTILE_RANKS, + schema: 'metric', + params: { + field: { + displayName: 'bytes', + format: { + convert: jest.fn(x => x), + }, + }, + customLabel: 'my custom field label', + values: [5000, 10000], + }, + }, + ], + null + ); + }); + + it('uses the custom label if it is set', function() { + const responseAggs: any = percentileRanksMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IPercentileRanksAggConfig); + + const percentileRankLabelFor5kBytes = responseAggs[0].makeLabel(); + const percentileRankLabelFor10kBytes = responseAggs[1].makeLabel(); + + expect(percentileRankLabelFor5kBytes).toBe('Percentile rank 5000 of "my custom field label"'); + expect(percentileRankLabelFor10kBytes).toBe('Percentile rank 10000 of "my custom field label"'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts index 6c069f8b70d62a..8b923092772dbf 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { PercentileRanksEditor } from '../../vis/editors/default/controls/percentile_ranks'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; +import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; @@ -30,7 +30,7 @@ import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; // required by the values editor -type IPercentileRanksAggConfig = IResponseAggConfig; +export type IPercentileRanksAggConfig = IResponseAggConfig; const valueProps = { makeLabel(this: IPercentileRanksAggConfig) { diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts new file mode 100644 index 00000000000000..1503f43b22dc30 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/percentiles.test.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 { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; +import { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypesMetricsPercentilesProvider class', () => { + let aggConfigs: AggConfigs; + + beforeEach(() => { + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.PERCENTILES, + type: METRIC_TYPES.PERCENTILES, + schema: 'metric', + params: { + field: { + displayName: 'bytes', + format: { + convert: jest.fn(x => `${x}th`), + }, + }, + customLabel: 'prince', + percents: [95], + }, + }, + ], + null + ); + }); + + it('uses the custom label if it is set', () => { + const responseAggs: any = percentilesMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IPercentileAggConfig); + + const ninetyFifthPercentileLabel = responseAggs[0].makeLabel(); + + expect(ninetyFifthPercentileLabel).toBe('95th percentile of prince'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.ts index 21cb3939d38f7f..0ac04554684729 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentiles.ts @@ -23,14 +23,14 @@ import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; -import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; +import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { PercentilesEditor } from '../../vis/editors/default/controls/percentiles'; // @ts-ignore import { ordinalSuffix } from '../../utils/ordinal_suffix'; -type IPercentileAggConfig = IResponseAggConfig; +export type IPercentileAggConfig = IResponseAggConfig; const valueProps = { makeLabel(this: IPercentileAggConfig) { diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts b/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts index 56e1f0456d62e7..c357d7bb0a903f 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts @@ -18,7 +18,7 @@ */ import { find } from 'lodash'; -import { IResponseAggConfig } from './get_response_agg_config_class'; +import { IResponseAggConfig } from './lib/get_response_agg_config_class'; export const getPercentileValue = ( agg: TAggConfig, diff --git a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts new file mode 100644 index 00000000000000..e038936de07d20 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts @@ -0,0 +1,188 @@ +/* + * 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 { spy } from 'sinon'; +import { bucketSumMetricAgg } from './bucket_sum'; +import { bucketAvgMetricAgg } from './bucket_avg'; +import { bucketMinMetricAgg } from './bucket_min'; +import { bucketMaxMetricAgg } from './bucket_max'; + +import { AggConfigs } from '../agg_configs'; +import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; + +jest.mock('../../vis/editors/default/schemas', () => { + class MockedSchemas { + all = [{}]; + } + return { + Schemas: jest.fn().mockImplementation(() => new MockedSchemas()), + }; +}); + +jest.mock('../../vis/editors/default/controls/sub_metric', () => { + return { + SubMetricParamEditor() {}, + }; +}); + +jest.mock('ui/new_platform'); + +describe('sibling pipeline aggs', () => { + const metrics = [ + { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, + { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, + { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, + { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, + ]; + + metrics.forEach(metric => { + describe(`${metric.title} metric`, () => { + let aggDsl: Record; + let metricAgg: MetricAggType; + let aggConfig: IMetricAggConfig; + + const init = (settings?: any) => { + const field = { + name: 'field', + format: { + type: { + id: 'bytes', + }, + }, + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: '1', + type: 'count', + schema: 'metric', + }, + { + id: '2', + type: metric.name, + schema: 'metric', + params: settings || { + customMetric: { + id: '5', + type: 'count', + schema: 'metric', + }, + customBucket: { + id: '6', + type: 'date_histogram', + schema: 'bucket', + params: { field: 'field', interval: '10s' }, + }, + }, + }, + ], + null + ); + + // Grab the aggConfig off the vis (we don't actually use the vis for anything else) + metricAgg = metric.provider; + aggConfig = aggConfigs.aggs[1]; + aggDsl = aggConfig.toDsl(aggConfigs); + }; + + it(`should return a label prefixed with ${metric.title} of`, () => { + init(); + + expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Count`); + }); + + it('should set parent aggs', function() { + init(); + + expect(aggDsl[metric.name].buckets_path).toBe('2-bucket>_count'); + expect(aggDsl.parentAggs['2-bucket'].date_histogram).not.toBeUndefined(); + }); + + it('should set nested parent aggs', () => { + init({ + customMetric: { + id: '5', + type: 'avg', + schema: 'metric', + params: { field: 'field' }, + }, + customBucket: { + id: '6', + type: 'date_histogram', + schema: 'bucket', + params: { field: 'field', interval: '10s' }, + }, + }); + + expect(aggDsl[metric.name].buckets_path).toBe('2-bucket>2-metric'); + expect(aggDsl.parentAggs['2-bucket'].date_histogram).not.toBeUndefined(); + expect(aggDsl.parentAggs['2-bucket'].aggs['2-metric'].avg.field).toEqual('field'); + }); + + it('should have correct formatter', () => { + init({ + customMetric: { + id: '5', + type: 'avg', + schema: 'metric', + params: { field: 'field' }, + }, + customBucket: { + id: '6', + type: 'date_histogram', + schema: 'bucket', + params: { field: 'field', interval: '10s' }, + }, + }); + + expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + }); + + it("should call modifyAggConfigOnSearchRequestStart for nested aggs' parameters", () => { + init(); + + const searchSource: any = {}; + const customMetricSpy = spy(); + const customBucketSpy = spy(); + const { customMetric, customBucket } = aggConfig.params; + + // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter + customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; + customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; + + aggConfig.type.params.forEach(param => { + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + }); + + expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + expect(customBucketSpy.calledWith(customBucket, searchSource)).toBe(true); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts new file mode 100644 index 00000000000000..ae09b5cd789774 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts @@ -0,0 +1,83 @@ +/* + * 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 { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; +import { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypeMetricStandardDeviationProvider class', () => { + const getAggConfigs = (customLabel?: string) => { + const field = { + name: 'memory', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.STD_DEV, + type: METRIC_TYPES.STD_DEV, + schema: 'metric', + params: { + field: { + displayName: 'memory', + }, + customLabel, + }, + }, + ], + null + ); + }; + + it('uses the custom label if it is set', () => { + const aggConfigs = getAggConfigs('custom label'); + const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IStdDevAggConfig); + + const lowerStdDevLabel = responseAggs[0].makeLabel(); + const upperStdDevLabel = responseAggs[1].makeLabel(); + + expect(lowerStdDevLabel).toBe('Lower custom label'); + expect(upperStdDevLabel).toBe('Upper custom label'); + }); + + it('uses the default labels if custom label is not set', () => { + const aggConfigs = getAggConfigs(); + + const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IStdDevAggConfig); + + const lowerStdDevLabel = responseAggs[0].makeLabel(); + const upperStdDevLabel = responseAggs[1].makeLabel(); + + expect(lowerStdDevLabel).toBe('Lower Standard Deviation of memory'); + expect(upperStdDevLabel).toBe('Upper Standard Deviation of memory'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.ts index 89051f119c588d..ebd5fceb9c7511 100644 --- a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts +++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.ts @@ -21,7 +21,7 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; +import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; interface ValProp { @@ -29,7 +29,7 @@ interface ValProp { title: string; } -interface IStdDevAggConfig extends IResponseAggConfig { +export interface IStdDevAggConfig extends IResponseAggConfig { keyedDetails: (customLabel: string, fieldDisplayName?: string) => { [key: string]: ValProp }; valProp: () => ValProp; } diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts new file mode 100644 index 00000000000000..e9d1ebb93d3ba6 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts @@ -0,0 +1,366 @@ +/* + * 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 { dropRight, last } from 'lodash'; +import { topHitMetricAgg } from './top_hit'; +import { AggConfigs } from '../agg_configs'; +import { IMetricAggConfig } from './metric_agg_type'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; + +jest.mock('ui/new_platform'); + +describe('Top hit metric', () => { + let aggDsl: Record; + let aggConfig: IMetricAggConfig; + + const init = ({ + fieldName = 'field', + sortOrder = 'desc', + aggregate = 'concat', + readFromDocValues = false, + fieldType = KBN_FIELD_TYPES.NUMBER, + size = 1, + }: any) => { + const field = { + name: fieldName, + displayName: fieldName, + type: fieldType, + readFromDocValues, + format: { + type: { + id: 'bytes', + }, + }, + }; + + const params = { + size, + field: fieldName, + sortField: field, + sortOrder: { + value: sortOrder, + }, + aggregate: { + value: aggregate, + }, + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + flattenHit: jest.fn(x => x!._source), + } as any; + + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: '1', + type: 'top_hits', + schema: 'metric', + params, + }, + ], + null + ); + + // Grab the aggConfig off the vis (we don't actually use the vis for anything else) + aggConfig = aggConfigs.aggs[0]; + aggDsl = aggConfig.toDsl(aggConfigs); + }; + + it('should return a label prefixed with Last if sorting in descending order', () => { + init({ fieldName: 'bytes' }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last bytes'); + }); + + it('should return a label prefixed with First if sorting in ascending order', () => { + init({ + fieldName: 'bytes', + sortOrder: 'asc', + }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First bytes'); + }); + + it('should request the _source field', () => { + init({ field: '_source' }); + expect(aggDsl.top_hits._source).toBeTruthy(); + expect(aggDsl.top_hits.docvalue_fields).toBeUndefined(); + }); + + it('requests both source and docvalues_fields for non-text aggregatable fields', () => { + init({ fieldName: 'bytes', readFromDocValues: true }); + expect(aggDsl.top_hits._source).toBe('bytes'); + expect(aggDsl.top_hits.docvalue_fields).toEqual([ + { field: 'bytes', format: 'use_field_mapping' }, + ]); + }); + + it('requests both source and docvalues_fields for date aggregatable fields', () => { + init({ fieldName: '@timestamp', readFromDocValues: true, fieldType: KBN_FIELD_TYPES.DATE }); + + expect(aggDsl.top_hits._source).toBe('@timestamp'); + expect(aggDsl.top_hits.docvalue_fields).toEqual([{ field: '@timestamp', format: 'date_time' }]); + }); + + it('requests just source for aggregatable text fields', () => { + init({ fieldName: 'machine.os' }); + expect(aggDsl.top_hits._source).toBe('machine.os'); + expect(aggDsl.top_hits.docvalue_fields).toBeUndefined(); + }); + + describe('try to get the value from the top hit', () => { + it('should return null if there is no hit', () => { + const bucket = { + '1': { + hits: { + hits: [], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(null); + }); + // + it('should return undefined if the field does not appear in the source', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: 123, + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + }); + + it('should return the field value from the top hit', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + '@tags': 'aaa', + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe('aaa'); + }); + + it('should return the object if the field value is an object', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + '@tags': { + label: 'aaa', + }, + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual({ label: 'aaa' }); + }); + + it('should return an array if the field has more than one values', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + '@tags': ['aaa', 'bbb'], + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(['aaa', 'bbb']); + }); + + it('should return undefined if the field is not in the source nor in the doc_values field', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: 12345, + }, + fields: { + bytes: 12345, + }, + }, + ], + }, + }, + }; + + init({ fieldName: 'machine.os.raw', readFromDocValues: true }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + }); + + describe('Multivalued field and first/last X docs', () => { + it('should return a label prefixed with Last X docs if sorting in descending order', () => { + init({ + fieldName: 'bytes', + size: 2, + }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last 2 bytes'); + }); + + it('should return a label prefixed with First X docs if sorting in ascending order', () => { + init({ + fieldName: 'bytes', + size: 2, + sortOrder: 'asc', + }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First 2 bytes'); + }); + + [ + { + description: 'concat values with a comma', + type: 'concat', + data: [1, 2, 3], + result: [1, 2, 3], + }, + { + description: 'sum up the values', + type: 'sum', + data: [1, 2, 3], + result: 6, + }, + { + description: 'take the minimum value', + type: 'min', + data: [1, 2, 3], + result: 1, + }, + { + description: 'take the maximum value', + type: 'max', + data: [1, 2, 3], + result: 3, + }, + { + description: 'take the average value', + type: 'average', + data: [1, 2, 3], + result: 2, + }, + { + description: 'support null/undefined', + type: 'min', + data: [undefined, null], + result: null, + }, + { + description: 'support null/undefined', + type: 'max', + data: [undefined, null], + result: null, + }, + { + description: 'support null/undefined', + type: 'sum', + data: [undefined, null], + result: null, + }, + { + description: 'support null/undefined', + type: 'average', + data: [undefined, null], + result: null, + }, + ].forEach(agg => { + it(`should return the result of the ${agg.type} aggregation over the last doc - ${agg.description}`, () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: agg.data, + }, + }, + ], + }, + }, + }; + + init({ fieldName: 'bytes', aggregate: agg.type }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + }); + + it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: dropRight(agg.data, 1), + }, + }, + { + _source: { + bytes: last(agg.data), + }, + }, + ], + }, + }, + }; + + init({ fieldName: 'bytes', aggregate: agg.type }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + }); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/param_types/json.test.ts b/src/legacy/ui/public/agg_types/param_types/json.test.ts index fb31385505a765..827299814c62aa 100644 --- a/src/legacy/ui/public/agg_types/param_types/json.test.ts +++ b/src/legacy/ui/public/agg_types/param_types/json.test.ts @@ -19,7 +19,7 @@ import { BaseParamType } from './base'; import { JsonParamType } from './json'; -import { AggConfig } from 'ui/agg_types'; +import { AggConfig } from '../agg_config'; jest.mock('ui/new_platform'); @@ -28,13 +28,12 @@ describe('JSON', function() { let aggConfig: AggConfig; let output: Record; - function initAggParam(config: Record = {}) { - return new JsonParamType({ + const initAggParam = (config: Record = {}) => + new JsonParamType({ ...config, type: 'json', name: paramName, }); - } beforeEach(function() { aggConfig = { params: {} } as AggConfig; diff --git a/src/legacy/ui/public/agg_types/param_types/string.test.ts b/src/legacy/ui/public/agg_types/param_types/string.test.ts index 3d496ecf898e43..fd5ccebde993eb 100644 --- a/src/legacy/ui/public/agg_types/param_types/string.test.ts +++ b/src/legacy/ui/public/agg_types/param_types/string.test.ts @@ -19,7 +19,7 @@ import { BaseParamType } from './base'; import { StringParamType } from './string'; -import { AggConfig } from 'ui/agg_types'; +import { AggConfig } from '../agg_config'; jest.mock('ui/new_platform'); @@ -28,13 +28,12 @@ describe('String', function() { let aggConfig: AggConfig; let output: Record; - function initAggParam(config: Record = {}) { - return new StringParamType({ + const initAggParam = (config: Record = {}) => + new StringParamType({ ...config, type: 'string', name: paramName, }); - } beforeEach(() => { aggConfig = { params: {} } as AggConfig; diff --git a/src/legacy/ui/public/agg_types/utils.ts b/src/legacy/ui/public/agg_types/utils.ts index c6452cf46e0c0c..6721262d265f46 100644 --- a/src/legacy/ui/public/agg_types/utils.ts +++ b/src/legacy/ui/public/agg_types/utils.ts @@ -49,7 +49,7 @@ function isValidJson(value: string): boolean { } } -function isValidInterval(value: string, baseInterval: string) { +function isValidInterval(value: string, baseInterval?: string) { if (baseInterval) { return _parseWithBase(value, baseInterval); } else { diff --git a/src/legacy/ui/public/autoload/modules.js b/src/legacy/ui/public/autoload/modules.js index d662d479fc86bd..e1d897236297eb 100644 --- a/src/legacy/ui/public/autoload/modules.js +++ b/src/legacy/ui/public/autoload/modules.js @@ -27,7 +27,6 @@ import '../promises'; import '../modals'; import '../state_management/app_state'; import '../state_management/global_state'; -import '../storage'; import '../style_compile'; import '../url'; import '../directives/watch_multi'; diff --git a/src/legacy/ui/public/autoload/settings.js b/src/legacy/ui/public/autoload/settings.js index c496839dda5d2d..50ae67c052b938 100644 --- a/src/legacy/ui/public/autoload/settings.js +++ b/src/legacy/ui/public/autoload/settings.js @@ -17,34 +17,4 @@ * under the License. */ -/** - * Autoload this file if we want some of the top level settings applied to a plugin. - * Currently this file makes sure the following settings are applied globally: - * - dateFormat:tz (meaning the Kibana time zone will be used in your plugin) - * - dateFormat:dow (meaning the Kibana configured start of the week will be used in your plugin) - */ - -import moment from 'moment-timezone'; -import chrome from '../chrome'; - -function setDefaultTimezone(tz) { - moment.tz.setDefault(tz); -} - -function setStartDayOfWeek(day) { - const dow = moment.weekdays().indexOf(day); - moment.updateLocale(moment.locale(), { week: { dow } }); -} - -const uiSettings = chrome.getUiSettingsClient(); - -setDefaultTimezone(uiSettings.get('dateFormat:tz')); -setStartDayOfWeek(uiSettings.get('dateFormat:dow')); - -uiSettings.getUpdate$().subscribe(({ key, newValue }) => { - if (key === 'dateFormat:tz') { - setDefaultTimezone(newValue); - } else if (key === 'dateFormat:dow') { - setStartDayOfWeek(newValue); - } -}); +/** Left intentionally empty to avoid breaking plugins that import this file during the NP migration */ diff --git a/src/legacy/ui/public/autoload/styles.js b/src/legacy/ui/public/autoload/styles.js index 540f65edbf4d68..c623acca07b015 100644 --- a/src/legacy/ui/public/autoload/styles.js +++ b/src/legacy/ui/public/autoload/styles.js @@ -17,9 +17,4 @@ * under the License. */ -// All Kibana styles inside of the /styles dir -const context = require.context('../styles', false, /[\/\\](?!mixins|variables|_|\.|bootstrap_(light|dark))[^\/\\]+\.less/); -context.keys().forEach(key => context(key)); - -// manually require non-less files -import '../styles/disable_animations'; +import 'ui/styles/font_awesome.less'; diff --git a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx index dccea2f61d5679..ff46b6ec34a86a 100644 --- a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx +++ b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx @@ -77,6 +77,7 @@ describe('injectUICapabilities', () => { uiCapabilities: UICapabilities; } + // eslint-disable-next-line react/prefer-stateless-function class MyClassComponent extends React.Component { public render() { return {this.props.uiCapabilities.uiCapability2.nestedProp}; diff --git a/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx index 21f96cf918afd7..76f1dd8016313e 100644 --- a/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx +++ b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx @@ -77,6 +77,7 @@ describe('injectUICapabilities', () => { uiCapabilities: UICapabilities; } + // eslint-disable-next-line react/prefer-stateless-function class MyClassComponent extends React.Component { public render() { return {this.props.uiCapabilities.uiCapability2.nestedProp}; diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx index fb6a1dc55c29ff..38711471074390 100644 --- a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx @@ -17,22 +17,14 @@ * under the License. */ -import React, { ReactNode } from 'react'; +import React from 'react'; import { UICapabilitiesContext } from './ui_capabilities_context'; import { capabilities } from '..'; -interface Props { - children: ReactNode; -} +export const UICapabilitiesProvider: React.SFC = props => ( + + {props.children} + +); -export class UICapabilitiesProvider extends React.Component { - public static displayName: string = 'UICapabilitiesProvider'; - - public render() { - return ( - - {this.props.children} - - ); - } -} +UICapabilitiesProvider.displayName = 'UICapabilitiesProvider'; diff --git a/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts b/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts index c362b1709fba69..f2c5fd5734b10f 100644 --- a/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts +++ b/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts @@ -19,7 +19,7 @@ import { httpServiceMock } from '../../../../../core/public/mocks'; -export const newPlatformHttp = httpServiceMock.createSetupContract(); +const newPlatformHttp = httpServiceMock.createSetupContract({ basePath: 'npBasePath' }); jest.doMock('ui/new_platform', () => ({ npSetup: { core: { http: newPlatformHttp }, diff --git a/src/legacy/ui/public/chrome/api/base_path.test.ts b/src/legacy/ui/public/chrome/api/base_path.test.ts index 812635ba364838..d3cfc6a3168a83 100644 --- a/src/legacy/ui/public/chrome/api/base_path.test.ts +++ b/src/legacy/ui/public/chrome/api/base_path.test.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import { newPlatformHttp } from './base_path.test.mocks'; +import './base_path.test.mocks'; import { initChromeBasePathApi } from './base_path'; function initChrome() { @@ -26,10 +25,6 @@ function initChrome() { return chrome; } -newPlatformHttp.basePath.get.mockImplementation(() => 'gotBasePath'); -newPlatformHttp.basePath.prepend.mockImplementation(() => 'addedToPath'); -newPlatformHttp.basePath.remove.mockImplementation(() => 'removedFromPath'); - beforeEach(() => { jest.clearAllMocks(); }); @@ -37,29 +32,20 @@ beforeEach(() => { describe('#getBasePath()', () => { it('proxies to newPlatformHttp.basePath.get()', () => { const chrome = initChrome(); - expect(newPlatformHttp.basePath.prepend).not.toHaveBeenCalled(); - expect(chrome.getBasePath()).toBe('gotBasePath'); - expect(newPlatformHttp.basePath.get).toHaveBeenCalledTimes(1); - expect(newPlatformHttp.basePath.get).toHaveBeenCalledWith(); + expect(chrome.getBasePath()).toBe('npBasePath'); }); }); describe('#addBasePath()', () => { it('proxies to newPlatformHttp.basePath.prepend(path)', () => { const chrome = initChrome(); - expect(newPlatformHttp.basePath.prepend).not.toHaveBeenCalled(); - expect(chrome.addBasePath('foo/bar')).toBe('addedToPath'); - expect(newPlatformHttp.basePath.prepend).toHaveBeenCalledTimes(1); - expect(newPlatformHttp.basePath.prepend).toHaveBeenCalledWith('foo/bar'); + expect(chrome.addBasePath('/foo/bar')).toBe('npBasePath/foo/bar'); }); }); describe('#removeBasePath', () => { it('proxies to newPlatformBasePath.basePath.remove(path)', () => { const chrome = initChrome(); - expect(newPlatformHttp.basePath.remove).not.toHaveBeenCalled(); - expect(chrome.removeBasePath('foo/bar')).toBe('removedFromPath'); - expect(newPlatformHttp.basePath.remove).toHaveBeenCalledTimes(1); - expect(newPlatformHttp.basePath.remove).toHaveBeenCalledWith('foo/bar'); + expect(chrome.removeBasePath('npBasePath/foo/bar')).toBe('/foo/bar'); }); }); diff --git a/src/legacy/ui/public/chrome/chrome.js b/src/legacy/ui/public/chrome/chrome.js index a5a0521013a6e1..d644965e092258 100644 --- a/src/legacy/ui/public/chrome/chrome.js +++ b/src/legacy/ui/public/chrome/chrome.js @@ -26,7 +26,7 @@ import '../config'; import '../notify'; import '../private'; import '../promises'; -import '../storage'; +import '../directives/storage'; import '../directives/watch_multi'; import './services'; import '../react_components'; diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.html b/src/legacy/ui/public/chrome/directives/kbn_chrome.html index 541082e68de58d..7738093fe9dea7 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.html +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.html @@ -1,4 +1,4 @@ -
+
npStart.core.chrome.docTitle; -// for karma test -export function setBaseTitle(str) { - baseTitle = str; -} - -let lastChange; - -function render() { - lastChange = lastChange || []; - - const parts = [lastChange[0]]; - - if (!lastChange[1]) parts.push(baseTitle); - - return _(parts).flattenDeep().compact().join(' - '); -} - -function change(title, complete) { - lastChange = [title, complete]; - update(); +function change(title) { + npDocTitle().change(isArray(title) ? title : [title]); } function reset() { - lastChange = null; -} - -function update() { - document.title = render(); + npDocTitle().reset(); } export const docTitle = { - render, change, reset, - update, }; uiModules.get('kibana') .run(function ($rootScope) { // always bind to the route events $rootScope.$on('$routeChangeStart', docTitle.reset); - $rootScope.$on('$routeChangeError', docTitle.update); - $rootScope.$on('$routeChangeSuccess', docTitle.update); }); diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap index b81e5b242d0248..796de886dc5bcd 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap @@ -125,6 +125,7 @@ exports[`UrlFormatEditor should render label template help 1`] = ` /> @@ -255,6 +256,7 @@ exports[`UrlFormatEditor should render normally 1`] = ` /> @@ -385,6 +387,7 @@ exports[`UrlFormatEditor should render url template help 1`] = ` /> @@ -564,6 +567,7 @@ exports[`UrlFormatEditor should render width and height fields if image 1`] = ` /> @@ -239,6 +239,7 @@ export class UrlFormatEditor extends DefaultFormatEditor { ); diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/samples/samples.js b/src/legacy/ui/public/field_editor/components/field_format_editor/samples/samples.js index 78e9a2a8f77a81..75bee9b1658fd1 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/samples/samples.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/samples/samples.js @@ -25,6 +25,9 @@ import { EuiBasicTable, EuiFormRow } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; export class FormatEditorSamplesComponent extends PureComponent { + static defaultProps = { + sampleType: 'text' + }; static propTypes = { samples: PropTypes.arrayOf( PropTypes.shape({ @@ -32,10 +35,11 @@ export class FormatEditorSamplesComponent extends PureComponent { output: PropTypes.any.isRequired, }) ).isRequired, + sampleType: PropTypes.oneOf(['html', 'text']), }; render() { - const { samples, intl } = this.props; + const { samples, intl, sampleType } = this.props; const columns = [ { @@ -55,15 +59,16 @@ export class FormatEditorSamplesComponent extends PureComponent { defaultMessage: 'Output', }), render: output => { - return ( -
- ); + dangerouslySetInnerHTML={{ __html: output }} //eslint-disable-line react/no-danger + /> + ) : (
{output}
); }, }, ]; diff --git a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js index a6bf2e7aa6c4c4..5b6455bf20847e 100644 --- a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js +++ b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js @@ -24,8 +24,8 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { getFilterGenerator } from '..'; import { FilterBarQueryFilterProvider } from '../../filter_manager/query_filter'; -import { uniqFilters } from '../../../../core_plugins/data/public/filter/filter_manager/lib/uniq_filters'; -import { getPhraseScript } from '@kbn/es-query'; +import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; + let queryFilter; let filterGen; let appState; @@ -137,14 +137,14 @@ describe('Filter Manager', function () { filterGen.add(scriptedField, 1, '+', 'myIndex'); checkAddFilters(1, [{ meta: { index: 'myIndex', negate: false, field: 'scriptedField' }, - script: getPhraseScript(scriptedField, 1) + script: esFilters.getPhraseScript(scriptedField, 1) }], 4); expect(appState.filters).to.have.length(3); filterGen.add(scriptedField, 1, '-', 'myIndex'); checkAddFilters(1, [{ meta: { index: 'myIndex', negate: true, disabled: false, field: 'scriptedField' }, - script: getPhraseScript(scriptedField, 1) + script: esFilters.getPhraseScript(scriptedField, 1) }], 5); expect(appState.filters).to.have.length(3); }); diff --git a/src/legacy/ui/public/filter_manager/filter_generator.js b/src/legacy/ui/public/filter_manager/filter_generator.js index f119a95833668b..e11e0ff6653a7e 100644 --- a/src/legacy/ui/public/filter_manager/filter_generator.js +++ b/src/legacy/ui/public/filter_manager/filter_generator.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getPhraseFilterField, getPhraseFilterValue, getPhraseScript, isPhraseFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../plugins/data/public'; // Adds a filter to a passed state export function getFilterGenerator(queryFilter) { @@ -42,8 +42,8 @@ export function getFilterGenerator(queryFilter) { return filter.exists.field === value; } - if (isPhraseFilter(filter)) { - return getPhraseFilterField(filter) === fieldName && getPhraseFilterValue(filter) === value; + if (esFilters.isPhraseFilter(filter)) { + return esFilters.getPhraseFilterField(filter) === fieldName && esFilters.getPhraseFilterValue(filter) === value; } if (filter.script) { @@ -73,7 +73,7 @@ export function getFilterGenerator(queryFilter) { if (field.scripted) { filter = { meta: { negate, index, field: fieldName }, - script: getPhraseScript(field, value) + script: esFilters.getPhraseScript(field, value) }; } else { filter = { meta: { negate, index }, query: { match_phrase: {} } }; diff --git a/src/legacy/ui/public/filter_manager/query_filter.js b/src/legacy/ui/public/filter_manager/query_filter.js index 6afe52502df5db..97b3810b7f1c73 100644 --- a/src/legacy/ui/public/filter_manager/query_filter.js +++ b/src/legacy/ui/public/filter_manager/query_filter.js @@ -18,12 +18,10 @@ */ import { FilterStateManager } from 'plugins/data'; +import { npStart } from 'ui/new_platform'; export function FilterBarQueryFilterProvider(getAppState, globalState) { - // TODO: this is imported here to avoid circular imports. - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { start } = require('../../../core_plugins/data/public/legacy'); - const filterManager = start.filter.filterManager; + const { filterManager } = npStart.plugins.data.query; const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = {}; diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 79365eb5cf1cc5..9c4cee6b05db04 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -20,11 +20,11 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; -import { TopNavMenu } from '../../../core_plugins/kibana_react/public'; +import { start as navigation } from '../../../core_plugins/navigation/public/legacy'; const module = uiModules.get('kibana'); -module.directive('kbnTopNav', () => { +export function createTopNavDirective() { return { restrict: 'E', template: '', @@ -71,9 +71,12 @@ module.directive('kbnTopNav', () => { return linkFn; } }; -}); +} -module.directive('kbnTopNavHelper', (reactDirective) => { +module.directive('kbnTopNav', createTopNavDirective); + +export function createTopNavHelper(reactDirective) { + const { TopNavMenu } = navigation.ui; return reactDirective( wrapInI18nContext(TopNavMenu), [ @@ -113,4 +116,6 @@ module.directive('kbnTopNavHelper', (reactDirective) => { 'showAutoRefreshOnly', ], ); -}); +} + +module.directive('kbnTopNavHelper', createTopNavHelper); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 8eac31e24530c7..58b8422cb2f8a9 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -18,6 +18,7 @@ */ import { + auto, ICompileProvider, IHttpProvider, IHttpService, @@ -48,6 +49,12 @@ import { isSystemApiRequest } from '../system_api'; const URL_LIMIT_WARN_WITHIN = 1000; +function isDummyWrapperRoute($route: any) { + return ( + $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ); +} + export const configureAppAngularModule = (angularModule: IModule) => { const newPlatform = npStart.core; const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); @@ -187,6 +194,9 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -226,6 +236,9 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -270,10 +283,16 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; $rootScope.$on('$routeChangeStart', () => { + if (isDummyWrapperRoute($route)) { + return; + } helpExtensionSetSinceRouteChange = false; }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -287,13 +306,16 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( $location: ILocationService, $rootScope: IRootScopeService, - Private: any, - config: any + $injector: auto.IInjectorService ) => { + const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { + if (isDummyWrapperRoute($route)) { + return; + } // disable long url checks when storing state in session storage - if (config.get('state:storeInSessionStorage')) { + if (newPlatform.uiSettings.get('state:storeInSessionStorage')) { return; } diff --git a/src/legacy/ui/public/modals/confirm_modal.js b/src/legacy/ui/public/modals/confirm_modal.js index 6d5abfca64aaf5..9c3f46da4e9275 100644 --- a/src/legacy/ui/public/modals/confirm_modal.js +++ b/src/legacy/ui/public/modals/confirm_modal.js @@ -36,16 +36,7 @@ export const ConfirmationButtonTypes = { CANCEL: CANCEL_BUTTON }; -/** - * @typedef {Object} ConfirmModalOptions - * @property {String} confirmButtonText - * @property {String=} cancelButtonText - * @property {function} onConfirm - * @property {function=} onCancel - * @property {String=} title - If given, shows a title on the confirm modal. - */ - -module.factory('confirmModal', function ($rootScope, $compile) { +export function confirmModalFactory($rootScope, $compile) { let modalPopover; const confirmQueue = []; @@ -114,4 +105,15 @@ module.factory('confirmModal', function ($rootScope, $compile) { } } }; -}); +} + +/** + * @typedef {Object} ConfirmModalOptions + * @property {String} confirmButtonText + * @property {String=} cancelButtonText + * @property {function} onConfirm + * @property {function=} onCancel + * @property {String=} title - If given, shows a title on the confirm modal. + */ + +module.factory('confirmModal', confirmModalFactory); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index ae5c0a83bd781f..bb055d6ce1e334 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -19,6 +19,12 @@ import sinon from 'sinon'; +const mockObservable = () => { + return { + subscribe: () => {} + }; +}; + export const npSetup = { core: { chrome: {} @@ -36,9 +42,25 @@ export const npSetup = { register: () => undefined, get: () => null, }, + getExecutor: () => ({ + interpreter: { + interpretAst: () => {}, + }, + }), }, }, data: { + autocomplete: { + addProvider: sinon.fake(), + getProvider: sinon.fake(), + }, + query: { + filterManager: sinon.fake(), + timefilter: { + timefilter: sinon.fake(), + history: sinon.fake(), + } + }, }, inspector: { registerView: () => undefined, @@ -56,6 +78,10 @@ export const npSetup = { }, }; +let refreshInterval = undefined; +let isTimeRangeSelectorEnabled = true; +let isAutoRefreshSelectorEnabled = true; + export const npStart = { core: { chrome: {} @@ -72,7 +98,63 @@ export const npStart = { registerType: sinon.fake(), }, data: { + autocomplete: { + getProvider: sinon.fake(), + }, getSuggestions: sinon.fake(), + query: { + filterManager: { + getFetches$: sinon.fake(), + getFilters: sinon.fake(), + getAppFilters: sinon.fake(), + getGlobalFilters: sinon.fake(), + removeFilter: sinon.fake(), + addFilters: sinon.fake(), + setFilters: sinon.fake(), + removeAll: sinon.fake(), + getUpdates$: mockObservable, + + }, + timefilter: { + timefilter: { + getFetch$: mockObservable, + getAutoRefreshFetch$: mockObservable, + getEnabledUpdated$: mockObservable, + getTimeUpdate$: mockObservable, + getRefreshIntervalUpdate$: mockObservable, + isTimeRangeSelectorEnabled: () => { + return isTimeRangeSelectorEnabled; + }, + isAutoRefreshSelectorEnabled: () => { + return isAutoRefreshSelectorEnabled; + }, + disableAutoRefreshSelector: () => { + isAutoRefreshSelectorEnabled = false; + }, + enableAutoRefreshSelector: () => { + isAutoRefreshSelectorEnabled = true; + }, + getRefreshInterval: () => { + return refreshInterval; + }, + setRefreshInterval: (interval) => { + refreshInterval = interval; + }, + enableTimeRangeSelector: () => { + isTimeRangeSelectorEnabled = true; + }, + disableTimeRangeSelector: () => { + isTimeRangeSelectorEnabled = false; + }, + getTime: sinon.fake(), + setTime: sinon.fake(), + getBounds: sinon.fake(), + calculateBounds: sinon.fake(), + createFilter: sinon.fake(), + }, + history: sinon.fake(), + }, + }, }, inspector: { isAvailable: () => false, @@ -96,6 +178,10 @@ export const npStart = { export function __setup__(coreSetup) { npSetup.core = coreSetup; + + // no-op application register calls (this is overwritten to + // bootstrap an LP plugin outside of tests) + npSetup.core.application.register = () => {}; } export function __start__(coreStart) { diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index b86f9cde0125c2..0c7b28e7da3df2 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -28,11 +28,16 @@ import { Start as InspectorStart, } from '../../../../plugins/inspector/public'; import { EuiUtilsStart } from '../../../../plugins/eui_utils/public'; +import { + FeatureCatalogueSetup, + FeatureCatalogueStart, +} from '../../../../plugins/feature_catalogue/public'; export interface PluginsSetup { data: ReturnType; embeddable: EmbeddableSetup; expressions: ReturnType; + feature_catalogue: FeatureCatalogueSetup; inspector: InspectorSetup; uiActions: IUiActionsSetup; } @@ -42,6 +47,7 @@ export interface PluginsStart { embeddable: EmbeddableStart; eui_utils: EuiUtilsStart; expressions: ReturnType; + feature_catalogue: FeatureCatalogueStart; inspector: InspectorStart; uiActions: IUiActionsStart; } diff --git a/src/legacy/ui/public/private/private.js b/src/legacy/ui/public/private/private.js index ef5c59c21dd7ac..6257c12ecf6961 100644 --- a/src/legacy/ui/public/private/private.js +++ b/src/legacy/ui/public/private/private.js @@ -108,98 +108,100 @@ function name(fn) { return fn.name || fn.toString().split('\n').shift(); } -uiModules.get('kibana/private') - .provider('Private', function () { - const provider = this; - - // one cache/swaps per Provider - const cache = {}; - const swaps = {}; +export function PrivateProvider() { + const provider = this; - // return the uniq id for this function - function identify(fn) { - if (typeof fn !== 'function') { - throw new TypeError('Expected private module "' + fn + '" to be a function'); - } + // one cache/swaps per Provider + const cache = {}; + const swaps = {}; - if (fn.$$id) return fn.$$id; - else return (fn.$$id = nextId()); + // return the uniq id for this function + function identify(fn) { + if (typeof fn !== 'function') { + throw new TypeError('Expected private module "' + fn + '" to be a function'); } - provider.stub = function (fn, instance) { - cache[identify(fn)] = instance; - return instance; - }; + if (fn.$$id) return fn.$$id; + else return (fn.$$id = nextId()); + } - provider.swap = function (fn, prov) { - const id = identify(fn); - swaps[id] = prov; - }; + provider.stub = function (fn, instance) { + cache[identify(fn)] = instance; + return instance; + }; - provider.$get = ['$injector', function PrivateFactory($injector) { + provider.swap = function (fn, prov) { + const id = identify(fn); + swaps[id] = prov; + }; - // prevent circular deps by tracking where we came from - const privPath = []; - const pathToString = function () { - return privPath.map(name).join(' -> '); - }; + provider.$get = ['$injector', function PrivateFactory($injector) { - // call a private provider and return the instance it creates - function instantiate(prov, locals) { - if (~privPath.indexOf(prov)) { - throw new Error( - 'Circular reference to "' + name(prov) + '"' + - ' found while resolving private deps: ' + pathToString() - ); - } + // prevent circular deps by tracking where we came from + const privPath = []; + const pathToString = function () { + return privPath.map(name).join(' -> '); + }; - privPath.push(prov); + // call a private provider and return the instance it creates + function instantiate(prov, locals) { + if (~privPath.indexOf(prov)) { + throw new Error( + 'Circular reference to "' + name(prov) + '"' + + ' found while resolving private deps: ' + pathToString() + ); + } - const context = {}; - let instance = $injector.invoke(prov, context, locals); - if (!_.isObject(instance)) instance = context; + privPath.push(prov); - privPath.pop(); - return instance; - } + const context = {}; + let instance = $injector.invoke(prov, context, locals); + if (!_.isObject(instance)) instance = context; - // retrieve an instance from cache or create and store on - function get(id, prov, $delegateId, $delegateProv) { - if (cache[id]) return cache[id]; + privPath.pop(); + return instance; + } - let instance; + // retrieve an instance from cache or create and store on + function get(id, prov, $delegateId, $delegateProv) { + if (cache[id]) return cache[id]; - if ($delegateId != null && $delegateProv != null) { - instance = instantiate(prov, { - $decorate: _.partial(get, $delegateId, $delegateProv) - }); - } else { - instance = instantiate(prov); - } + let instance; - return (cache[id] = instance); + if ($delegateId != null && $delegateProv != null) { + instance = instantiate(prov, { + $decorate: _.partial(get, $delegateId, $delegateProv) + }); + } else { + instance = instantiate(prov); } - // main api, get the appropriate instance for a provider - function Private(prov) { - let id = identify(prov); - let $delegateId; - let $delegateProv; + return (cache[id] = instance); + } - if (swaps[id]) { - $delegateId = id; - $delegateProv = prov; + // main api, get the appropriate instance for a provider + function Private(prov) { + let id = identify(prov); + let $delegateId; + let $delegateProv; - prov = swaps[$delegateId]; - id = identify(prov); - } + if (swaps[id]) { + $delegateId = id; + $delegateProv = prov; - return get(id, prov, $delegateId, $delegateProv); + prov = swaps[$delegateId]; + id = identify(prov); } - Private.stub = provider.stub; - Private.swap = provider.swap; + return get(id, prov, $delegateId, $delegateProv); + } + + Private.stub = provider.stub; + Private.swap = provider.swap; + + return Private; + }]; +} - return Private; - }]; - }); +uiModules.get('kibana/private') + .provider('Private', PrivateProvider); diff --git a/src/legacy/ui/public/promises/promises.js b/src/legacy/ui/public/promises/promises.js index 99c9a11be74314..af8a5081e0c550 100644 --- a/src/legacy/ui/public/promises/promises.js +++ b/src/legacy/ui/public/promises/promises.js @@ -22,9 +22,7 @@ import { uiModules } from '../modules'; const module = uiModules.get('kibana'); -// Provides a tiny subset of the excellent API from -// bluebird, reimplemented using the $q service -module.service('Promise', function ($q, $timeout) { +export function PromiseServiceCreator($q, $timeout) { function Promise(fn) { if (typeof this === 'undefined') throw new Error('Promise constructor must be called with "new"'); @@ -122,4 +120,8 @@ module.service('Promise', function ($q, $timeout) { }; return Promise; -}); +} + +// Provides a tiny subset of the excellent API from +// bluebird, reimplemented using the $q service +module.service('Promise', PromiseServiceCreator); diff --git a/src/legacy/ui/public/registry/feature_catalogue.js b/src/legacy/ui/public/registry/feature_catalogue.js index 0eb8a4a3345217..8905a15106953f 100644 --- a/src/legacy/ui/public/registry/feature_catalogue.js +++ b/src/legacy/ui/public/registry/feature_catalogue.js @@ -19,6 +19,7 @@ import { uiRegistry } from './_registry'; import { capabilities } from '../capabilities'; +export { FeatureCatalogueCategory } from '../../../../plugins/feature_catalogue/public'; export const FeatureCatalogueRegistryProvider = uiRegistry({ name: 'featureCatalogue', @@ -30,9 +31,3 @@ export const FeatureCatalogueRegistryProvider = uiRegistry({ return !isDisabledViaCapabilities && Object.keys(featureCatalogItem).length > 0; } }); - -export const FeatureCatalogueCategory = { - ADMIN: 'admin', - DATA: 'data', - OTHER: 'other' -}; diff --git a/src/legacy/ui/public/registry/navbar_extensions.js b/src/legacy/ui/public/registry/navbar_extensions.js deleted file mode 100644 index 6c7911c6f0f521..00000000000000 --- a/src/legacy/ui/public/registry/navbar_extensions.js +++ /dev/null @@ -1,28 +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 { uiRegistry } from './_registry'; - -export const NavBarExtensionsRegistryProvider = uiRegistry({ - name: 'navbarExtensions', - index: ['name'], - group: ['appName'], - order: ['order'] -}); - diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index 3471d7e954862f..56203354f3c203 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -26,7 +26,10 @@ import { ChromeBreadcrumb } from '../../../../core/public'; interface RouteConfiguration { controller?: string | ((...args: any[]) => void); redirectTo?: string; + resolveRedirectTo?: (...args: any[]) => void; reloadOnSearch?: boolean; + reloadOnUrl?: boolean; + outerAngularWrapperRoute?: boolean; resolve?: object; template?: string; k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; diff --git a/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx b/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx index 6aea3c72e0c34a..3c691c692948a7 100644 --- a/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx +++ b/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx @@ -34,7 +34,7 @@ function isSuccess(result: SaveResult): result is { id?: string } { return 'id' in result; } -interface MinimalSaveModalProps { +export interface MinimalSaveModalProps { onSave: (...args: any[]) => Promise; onClose: () => void; } diff --git a/src/legacy/ui/public/state_management/config_provider.js b/src/legacy/ui/public/state_management/config_provider.js index 090210cc8723e8..ec770e7fef6caf 100644 --- a/src/legacy/ui/public/state_management/config_provider.js +++ b/src/legacy/ui/public/state_management/config_provider.js @@ -25,21 +25,23 @@ import { uiModules } from '../modules'; -uiModules.get('kibana/state_management') - .provider('stateManagementConfig', class StateManagementConfigProvider { - _enabled = true +export class StateManagementConfigProvider { + _enabled = true + + $get(/* inject stuff */) { + return { + enabled: this._enabled, + }; + } - $get(/* inject stuff */) { - return { - enabled: this._enabled, - }; - } + disable() { + this._enabled = false; + } - disable() { - this._enabled = false; - } + enable() { + this._enabled = true; + } +} - enable() { - this._enabled = true; - } - }); +uiModules.get('kibana/state_management') + .provider('stateManagementConfig', StateManagementConfigProvider); diff --git a/src/legacy/ui/public/storage/directive.js b/src/legacy/ui/public/storage/directive.js deleted file mode 100644 index a5bb2ee3b6b0bd..00000000000000 --- a/src/legacy/ui/public/storage/directive.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -import { uiModules } from '../modules'; -import { Storage } from './storage'; - -const createService = function (type) { - return function ($window) { - return new Storage($window[type]); - }; -}; - -uiModules.get('kibana/storage') - .service('localStorage', createService('localStorage')) - .service('sessionStorage', createService('sessionStorage')); diff --git a/src/legacy/ui/public/storage/index.ts b/src/legacy/ui/public/storage/index.ts deleted file mode 100644 index 17bbb61b2b8d5a..00000000000000 --- a/src/legacy/ui/public/storage/index.ts +++ /dev/null @@ -1,22 +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 './directive'; - -export { Storage } from './storage'; diff --git a/src/legacy/ui/public/storage/storage.ts b/src/legacy/ui/public/storage/storage.ts deleted file mode 100644 index 703886c1e034c4..00000000000000 --- a/src/legacy/ui/public/storage/storage.ts +++ /dev/null @@ -1,66 +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 angular from 'angular'; - -// This is really silly, but I wasn't prepared to rename the kibana Storage class everywhere it is used -// and this is the only way I could figure out how to use the type definition for a built in object -// in a file that creates a type with the same name as that built in object. -import { WebStorage } from './web_storage'; - -export class Storage { - public store: WebStorage; - - constructor(store: WebStorage) { - this.store = store; - } - - public get = (key: string) => { - if (!this.store) { - return null; - } - - const storageItem = this.store.getItem(key); - if (storageItem === null) { - return null; - } - - try { - return JSON.parse(storageItem); - } catch (error) { - return null; - } - }; - - public set = (key: string, value: any) => { - try { - return this.store.setItem(key, angular.toJson(value)); - } catch (e) { - return false; - } - }; - - public remove = (key: string) => { - return this.store.removeItem(key); - }; - - public clear = () => { - return this.store.clear(); - }; -} diff --git a/src/legacy/ui/public/storage/web_storage.ts b/src/legacy/ui/public/storage/web_storage.ts deleted file mode 100644 index d5f775431143d9..00000000000000 --- a/src/legacy/ui/public/storage/web_storage.ts +++ /dev/null @@ -1,20 +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. - */ - -export type WebStorage = Storage; diff --git a/src/legacy/ui/public/styles/_legacy/_base.scss b/src/legacy/ui/public/styles/_legacy/_base.scss index aad90a99ac3576..0fcfb515c7c90b 100644 --- a/src/legacy/ui/public/styles/_legacy/_base.scss +++ b/src/legacy/ui/public/styles/_legacy/_base.scss @@ -4,13 +4,14 @@ input.ng-invalid, textarea.ng-invalid, select.ng-invalid { - &.ng-dirty, &.ng-touched { + &.ng-dirty, + &.ng-touched { border-color: $euiColorDanger !important; } } -input[type="radio"], -input[type="checkbox"], +input[type='radio'], +input[type='checkbox'], .radio, .radio-inline, .checkbox, @@ -18,7 +19,7 @@ input[type="checkbox"], &[disabled], fieldset[disabled] & { cursor: default; - opacity: .8; + opacity: 0.8; } } @@ -27,7 +28,7 @@ input[type="checkbox"], align-items: center; padding-left: 0 !important; - input[type="checkbox"] { + input[type='checkbox'] { float: none; margin: 0 $euiSizeXS; position: static; @@ -95,7 +96,6 @@ input[type="checkbox"], } } - // Too overused in many places to be moved elsewhere .page-row { @@ -114,7 +114,7 @@ input[type="checkbox"], // state. This is useful when you've already hand crafted your own // focus states in Kibana. :focus { - &:not([class^="eui"]):not(.kbn-resetFocusState) { + &:not([class^='eui']):not(.kbn-resetFocusState) { @include euiFocusRing; } } @@ -122,7 +122,8 @@ input[type="checkbox"], // A neccessary hack so that the above focus policy doesn't polute some EUI // entrenched inputs. .euiComboBox { - input:focus { + // :not() specificity needed to override the above + input:not([class^='eui']):focus { animation: none !important; } } diff --git a/src/legacy/ui/public/styles/disable_animations/disable_animations.js b/src/legacy/ui/public/styles/disable_animations/disable_animations.js deleted file mode 100644 index c70aaa2165691f..00000000000000 --- a/src/legacy/ui/public/styles/disable_animations/disable_animations.js +++ /dev/null @@ -1,46 +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 chrome from 'ui/chrome'; -import disableAnimationsCss from '!!raw-loader!./disable_animations.css'; - -const uiSettings = chrome.getUiSettingsClient(); - -// rather than silently ignore when the style element is missing in the tests -// like ui/theme does, we automatically create a style tag because ordering doesn't -// really matter for these styles, they should really take top priority because -// they all use `!important`, and we don't want to silently ignore the possibility -// of accidentally removing the style element from the chrome template. -const styleElement = document.createElement('style'); -styleElement.setAttribute('id', 'disableAnimationsCss'); -document.head.appendChild(styleElement); - -function updateStyleSheet() { - styleElement.textContent = uiSettings.get('accessibility:disableAnimations') - ? disableAnimationsCss - : ''; -} - -updateStyleSheet(); -uiSettings.getUpdate$().subscribe(({ key }) => { - if (key === 'accessibility:disableAnimations') { - updateStyleSheet(); - } -}); - diff --git a/src/legacy/ui/public/styles/disable_animations/index.js b/src/legacy/ui/public/styles/disable_animations/index.js deleted file mode 100644 index 2c459667d59969..00000000000000 --- a/src/legacy/ui/public/styles/disable_animations/index.js +++ /dev/null @@ -1,20 +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 './disable_animations'; diff --git a/src/legacy/ui/public/timefilter/index.ts b/src/legacy/ui/public/timefilter/index.ts index c102d979c951a0..82e2531ec62a68 100644 --- a/src/legacy/ui/public/timefilter/index.ts +++ b/src/legacy/ui/public/timefilter/index.ts @@ -18,20 +18,20 @@ */ import uiRoutes from 'ui/routes'; -import { TimefilterContract, TimeHistoryContract } from '../../../core_plugins/data/public'; +import { npStart } from 'ui/new_platform'; +import { TimefilterContract, TimeHistoryContract } from '../../../../plugins/data/public'; import { registerTimefilterWithGlobalState } from './setup_router'; -import { start as data } from '../../../core_plugins/data/public/legacy'; export { getTime, InputTimeRange, TimeHistoryContract, TimefilterContract, -} from '../../../core_plugins/data/public'; +} from '../../../../plugins/data/public'; export type Timefilter = TimefilterContract; export type TimeHistory = TimeHistoryContract; -export const timeHistory = data.timefilter.history; -export const timefilter = data.timefilter.timefilter; +export const timeHistory = npStart.plugins.data.query.timefilter.history; +export const timefilter = npStart.plugins.data.query.timefilter.timefilter; uiRoutes.addSetupWork((globalState, $rootScope) => { return registerTimefilterWithGlobalState(timefilter, globalState, $rootScope); diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts index ffc8a1fca6c641..0a73378f99cd7d 100644 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ b/src/legacy/ui/public/timefilter/setup_router.ts @@ -22,8 +22,7 @@ import { IScope } from 'angular'; import moment from 'moment'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import chrome from 'ui/chrome'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -import { TimefilterContract } from '../../../core_plugins/data/public/timefilter'; +import { RefreshInterval, TimeRange, TimefilterContract } from 'src/plugins/data/public'; // TODO // remove everything underneath once globalState is no longer an angular service diff --git a/src/legacy/ui/public/vis/default_feedback_message.js b/src/legacy/ui/public/vis/default_feedback_message.js index f840ba961b9c3f..8b8491d397aad5 100644 --- a/src/legacy/ui/public/vis/default_feedback_message.js +++ b/src/legacy/ui/public/vis/default_feedback_message.js @@ -19,9 +19,10 @@ import { i18n } from '@kbn/i18n'; -export const defaultFeedbackMessage = i18n.translate('common.ui.vis.defaultFeedbackMessage', - { - defaultMessage: 'Have feedback? Please create an issue in {link}.', - values: { link: 'GitHub' } - } -); +export const defaultFeedbackMessage = i18n.translate('common.ui.vis.defaultFeedbackMessage', { + defaultMessage: 'Have feedback? Please create an issue in {link}.', + values: { + link: + 'GitHub', + }, +}); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx index 5dfb14156deeea..7806b1c0f78fbc 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx @@ -156,7 +156,7 @@ describe('DefaultEditorAgg component', () => { it('should add schema component', () => { defaultProps.agg.schema = { - editorComponent: () =>
, + editorComponent: () =>
, } as any; const comp = mount(); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx index 6d3b5fc4e63557..528914f4fd006b 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx @@ -26,7 +26,9 @@ import { EuiDraggable, EuiSpacer, EuiPanel, + EuiFormErrorText, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { AggConfig } from '../../../../agg_types/agg_config'; import { aggGroupNamesMap, AggGroupNames } from '../agg_groups'; @@ -80,7 +82,15 @@ function DefaultEditorAggGroup({ const [aggsState, setAggsState] = useReducer(aggGroupReducer, group, initAggsState); - const isGroupValid = Object.values(aggsState).every(item => item.valid); + const bucketsError = + lastParentPipelineAggTitle && groupName === AggGroupNames.Buckets && !group.length + ? i18n.translate('common.ui.aggTypes.buckets.mustHaveBucketErrorMessage', { + defaultMessage: 'Add a bucket with "Date Histogram" or "Histogram" aggregation.', + description: 'Date Histogram and Histogram should not be translated', + }) + : undefined; + + const isGroupValid = !bucketsError && Object.values(aggsState).every(item => item.valid); const isAllAggsTouched = isInvalidAggsTouched(aggsState); const isMetricAggregationDisabled = useMemo( () => groupName === AggGroupNames.Metrics && getEnabledMetricAggsCount(group) === 1, @@ -144,6 +154,12 @@ function DefaultEditorAggGroup({

{groupNameLabel}

+ {bucketsError && ( + <> + {bucketsError} + + + )} <> {group.map((agg: AggConfig, index: number) => ( diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx index 6513c9211f18b9..4f4c0bda6520ab 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx @@ -21,9 +21,10 @@ import React, { useReducer, useEffect, useMemo } from 'react'; import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { aggTypes, AggType, AggParam } from 'ui/agg_types'; +import { VisState } from 'ui/vis'; +import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; import { IndexPattern } from 'ui/index_patterns'; -import { AggConfig, VisState } from '../../..'; + import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; import { diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap new file mode 100644 index 00000000000000..ab192e6fd3cbb8 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NumberList should be rendered with default set of props 1`] = ` + + + + + + + + + + + +`; diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_row.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_row.test.tsx.snap new file mode 100644 index 00000000000000..3882425594cf37 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_row.test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NumberRow should be rendered with default set of props 1`] = ` + + + + + + + + +`; diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.test.tsx new file mode 100644 index 00000000000000..3faf164c365d9d --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.test.tsx @@ -0,0 +1,147 @@ +/* + * 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 { shallow } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import { NumberList, NumberListProps } from './number_list'; +import { NumberRow } from './number_row'; + +jest.mock('./number_row', () => ({ + NumberRow: () => 'NumberRow', +})); + +jest.mock('@elastic/eui', () => ({ + htmlIdGenerator: jest.fn(() => { + let counter = 1; + return () => `12${counter++}`; + }), + EuiSpacer: require.requireActual('@elastic/eui').EuiSpacer, + EuiFlexItem: require.requireActual('@elastic/eui').EuiFlexItem, + EuiButtonEmpty: require.requireActual('@elastic/eui').EuiButtonEmpty, + EuiFormErrorText: require.requireActual('@elastic/eui').EuiFormErrorText, +})); + +describe('NumberList', () => { + let defaultProps: NumberListProps; + + beforeEach(() => { + defaultProps = { + labelledbyId: 'numberList', + numberArray: [1, 2], + range: '[1, 10]', + showValidation: false, + unitName: 'value', + onChange: jest.fn(), + setTouched: jest.fn(), + setValidity: jest.fn(), + }; + }); + + test('should be rendered with default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + test('should show an order error', () => { + defaultProps.numberArray = [3, 1]; + defaultProps.showValidation = true; + const comp = mountWithIntl(); + + expect(comp.find('EuiFormErrorText').length).toBe(1); + }); + + test('should set validity as true', () => { + mountWithIntl(); + + expect(defaultProps.setValidity).lastCalledWith(true); + }); + + test('should set validity as false when the order is invalid', () => { + defaultProps.numberArray = [3, 2]; + const comp = mountWithIntl(); + + expect(defaultProps.setValidity).lastCalledWith(false); + + comp.setProps({ numberArray: [1, 2] }); + expect(defaultProps.setValidity).lastCalledWith(true); + }); + + test('should set validity as false when there is an empty field', () => { + defaultProps.numberArray = [1, 2]; + const comp = mountWithIntl(); + + comp.setProps({ numberArray: [1, undefined] }); + expect(defaultProps.setValidity).lastCalledWith(false); + }); + + test('should set 0 when number array is empty', () => { + defaultProps.numberArray = []; + mountWithIntl(); + + expect(defaultProps.onChange).lastCalledWith([0]); + }); + + test('should add a number', () => { + const comp = shallow(); + comp.find('EuiButtonEmpty').simulate('click'); + + expect(defaultProps.onChange).lastCalledWith([1, 2, 3]); + }); + + test('should remove a number', () => { + const comp = shallow(); + const row = comp.find(NumberRow).first(); + row.prop('onDelete')(row.prop('model').id); + + expect(defaultProps.onChange).lastCalledWith([2]); + }); + + test('should disable remove button if there is one number', () => { + defaultProps.numberArray = [1]; + const comp = shallow(); + + expect( + comp + .find(NumberRow) + .first() + .prop('disableDelete') + ).toEqual(true); + }); + + test('should change value', () => { + const comp = shallow(); + const row = comp.find(NumberRow).first(); + row.prop('onChange')({ id: row.prop('model').id, value: '3' }); + + expect(defaultProps.onChange).lastCalledWith([3, 2]); + }); + + test('should call setTouched', () => { + const comp = shallow(); + comp + .find(NumberRow) + .first() + .prop('onBlur')(); + + expect(defaultProps.setTouched).toBeCalled(); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx index 54040814028f71..4aae217c1bc8d6 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useMemo, useCallback } from 'react'; import { EuiSpacer, EuiButtonEmpty, EuiFlexItem, EuiFormErrorText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -34,16 +34,15 @@ import { getUpdatedModels, hasInvalidValues, } from './utils'; +import { useValidation } from '../../agg_utils'; -interface NumberListProps { +export interface NumberListProps { labelledbyId: string; numberArray: Array; range?: string; showValidation: boolean; unitName: string; validateAscendingOrder?: boolean; - onBlur?(): void; - onFocus?(): void; onChange(list: Array): void; setTouched(): void; setValidity(isValid: boolean): void; @@ -56,81 +55,84 @@ function NumberList({ showValidation, unitName, validateAscendingOrder = true, - onBlur, - onFocus, onChange, setTouched, setValidity, }: NumberListProps) { - const numberRange = getRange(range); + const numberRange = useMemo(() => getRange(range), [range]); const [models, setModels] = useState(getInitModelList(numberArray)); const [ascendingError, setAscendingError] = useState(EMPTY_STRING); - // responsible for discarding changes + // set up validity for each model useEffect(() => { - const updatedModels = getUpdatedModels(numberArray, models, numberRange); + let id: number | undefined; if (validateAscendingOrder) { - const isOrderValid = validateOrder(updatedModels); + const { isValidOrder, modelIndex } = validateOrder(numberArray); + id = isValidOrder ? undefined : modelIndex; setAscendingError( - isOrderValid - ? i18n.translate('common.ui.aggTypes.numberList.invalidAscOrderErrorMessage', { + isValidOrder + ? EMPTY_STRING + : i18n.translate('common.ui.aggTypes.numberList.invalidAscOrderErrorMessage', { defaultMessage: 'The values should be in ascending order.', }) - : EMPTY_STRING ); } - setModels(updatedModels); - }, [numberArray]); + setModels(state => getUpdatedModels(numberArray, state, numberRange, id)); + }, [numberArray, numberRange, validateAscendingOrder]); + // responsible for setting up an initial value ([0]) when there is no default value useEffect(() => { - setValidity(!hasInvalidValues(models)); - }, [models]); - - // resposible for setting up an initial value ([0]) when there is no default value - useEffect(() => { - onChange(models.map(({ value }) => (value === EMPTY_STRING ? undefined : value))); + if (!numberArray.length) { + onChange([models[0].value as number]); + } }, []); - const onChangeValue = ({ id, value }: { id: string; value: string }) => { - const parsedValue = parse(value); - const { isValid, errors } = validateValue(parsedValue, numberRange); - setValidity(isValid); + const isValid = !hasInvalidValues(models); + useValidation(setValidity, isValid); - const currentModel = models.find(model => model.id === id); - if (currentModel) { - currentModel.value = parsedValue; - currentModel.isInvalid = !isValid; - currentModel.errors = errors; - } + const onUpdate = useCallback( + (modelList: NumberRowModel[]) => { + setModels(modelList); + onChange(modelList.map(({ value }) => (value === EMPTY_STRING ? undefined : value))); + }, + [onChange] + ); - onUpdate(models); - }; + const onChangeValue = useCallback( + ({ id, value }: { id: string; value: string }) => { + const parsedValue = parse(value); + + onUpdate( + models.map(model => { + if (model.id === id) { + const { isInvalid, error } = validateValue(parsedValue, numberRange); + return { + id, + value: parsedValue, + isInvalid, + error, + }; + } + return model; + }) + ); + }, + [numberRange, models, onUpdate] + ); // Add an item to the end of the list - const onAdd = () => { + const onAdd = useCallback(() => { const newArray = [...models, getNextModel(models, numberRange)]; onUpdate(newArray); - }; - - const onDelete = (id: string) => { - const newArray = models.filter(model => model.id !== id); - onUpdate(newArray); - }; - - const onBlurFn = (model: NumberRowModel) => { - if (model.value === EMPTY_STRING) { - model.isInvalid = true; - } - setTouched(); - if (onBlur) { - onBlur(); - } - }; - - const onUpdate = (modelList: NumberRowModel[]) => { - setModels(modelList); - onChange(modelList.map(({ value }) => (value === EMPTY_STRING ? undefined : value))); - }; + }, [models, numberRange, onUpdate]); + + const onDelete = useCallback( + (id: string) => { + const newArray = models.filter(model => model.id !== id); + onUpdate(newArray); + }, + [models, onUpdate] + ); return ( <> @@ -143,13 +145,12 @@ function NumberList({ labelledbyId={labelledbyId} range={numberRange} onDelete={onDelete} - onFocus={onFocus} onChange={onChangeValue} - onBlur={() => onBlurFn(model)} + onBlur={setTouched} autoFocus={models.length !== 1 && arrayIndex === models.length - 1} /> - {showValidation && model.isInvalid && model.errors && model.errors.length > 0 && ( - {model.errors.join('\n')} + {showValidation && model.isInvalid && model.error && ( + {model.error} )} {models.length - 1 !== arrayIndex && } diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.test.tsx new file mode 100644 index 00000000000000..2173d92819e3d3 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { NumberRow, NumberRowProps } from './number_row'; + +describe('NumberRow', () => { + let defaultProps: NumberRowProps; + + beforeEach(() => { + defaultProps = { + autoFocus: false, + disableDelete: false, + isInvalid: false, + labelledbyId: 'numberList', + model: { value: 1, id: '1', isInvalid: false }, + range: { + min: 1, + max: 10, + minInclusive: true, + maxInclusive: true, + within: jest.fn(() => true), + }, + onChange: jest.fn(), + onBlur: jest.fn(), + onDelete: jest.fn(), + }; + }); + + test('should be rendered with default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + test('should call onDelete', () => { + const comp = shallow(); + comp.find('EuiButtonIcon').simulate('click'); + + expect(defaultProps.onDelete).lastCalledWith(defaultProps.model.id); + }); + + test('should call onChange', () => { + const comp = shallow(); + comp.find('EuiFieldNumber').prop('onChange')!({ target: { value: '5' } } as React.ChangeEvent< + HTMLInputElement + >); + + expect(defaultProps.onChange).lastCalledWith({ id: defaultProps.model.id, value: '5' }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx index cef574e806e019..23e671180e9802 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx @@ -17,13 +17,13 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Range } from '../../../../../../utils/range'; -interface NumberRowProps { +export interface NumberRowProps { autoFocus: boolean; disableDelete: boolean; isInvalid: boolean; @@ -31,7 +31,6 @@ interface NumberRowProps { model: NumberRowModel; range: Range; onBlur(): void; - onFocus?(): void; onChange({ id, value }: { id: string; value: string }): void; onDelete(index: string): void; } @@ -40,7 +39,7 @@ export interface NumberRowModel { id: string; isInvalid: boolean; value: number | ''; - errors?: string[]; + error?: string; } function NumberRow({ @@ -52,7 +51,6 @@ function NumberRow({ range, onBlur, onDelete, - onFocus, onChange, }: NumberRowProps) { const deleteBtnAriaLabel = i18n.translate( @@ -63,11 +61,16 @@ function NumberRow({ } ); - const onValueChanged = (event: React.ChangeEvent) => - onChange({ - value: event.target.value, - id: model.id, - }); + const onValueChanged = useCallback( + (event: React.ChangeEvent) => + onChange({ + value: event.target.value, + id: model.id, + }), + [onChange, model.id] + ); + + const onDeleteFn = useCallback(() => onDelete(model.id), [onDelete, model.id]); return ( @@ -81,7 +84,6 @@ function NumberRow({ defaultMessage: 'Enter a value', })} onChange={onValueChanged} - onFocus={onFocus} value={model.value} fullWidth={true} min={range.min} @@ -95,7 +97,7 @@ function NumberRow({ title={deleteBtnAriaLabel} color="danger" iconType="trash" - onClick={() => onDelete(model.id)} + onClick={onDeleteFn} disabled={disableDelete} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts new file mode 100644 index 00000000000000..3928c0a62eeaa7 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts @@ -0,0 +1,218 @@ +/* + * 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 { + getInitModelList, + getUpdatedModels, + validateOrder, + hasInvalidValues, + parse, + validateValue, + getNextModel, + getRange, +} from './utils'; +import { Range } from '../../../../../../utils/range'; +import { NumberRowModel } from './number_row'; + +describe('NumberList utils', () => { + let modelList: NumberRowModel[]; + let range: Range; + + beforeEach(() => { + modelList = [{ value: 1, id: '1', isInvalid: false }, { value: 2, id: '2', isInvalid: false }]; + range = { + min: 1, + max: 10, + minInclusive: true, + maxInclusive: true, + within: jest.fn(() => true), + }; + }); + + describe('getInitModelList', () => { + test('should return list with default model when number list is empty', () => { + const models = getInitModelList([]); + + expect(models).toEqual([{ value: 0, id: expect.any(String), isInvalid: false }]); + }); + + test('should return model list', () => { + const models = getInitModelList([1, undefined]); + + expect(models).toEqual([ + { value: 1, id: expect.any(String), isInvalid: false }, + { value: '', id: expect.any(String), isInvalid: false }, + ]); + }); + }); + + describe('getUpdatedModels', () => { + test('should return model list when number list is empty', () => { + const updatedModelList = getUpdatedModels([], modelList, range); + + expect(updatedModelList).toEqual([{ value: 0, id: expect.any(String), isInvalid: false }]); + }); + + test('should not update model list when number list is the same', () => { + const updatedModelList = getUpdatedModels([1, 2], modelList, range); + + expect(updatedModelList).toEqual(modelList); + }); + + test('should update model list when number list was changed', () => { + const updatedModelList = getUpdatedModels([1, 3], modelList, range); + modelList[1].value = 3; + expect(updatedModelList).toEqual(modelList); + }); + + test('should update model list when number list increased', () => { + const updatedModelList = getUpdatedModels([1, 2, 3], modelList, range); + expect(updatedModelList).toEqual([ + ...modelList, + { value: 3, id: expect.any(String), isInvalid: false }, + ]); + }); + + test('should update model list when number list decreased', () => { + const updatedModelList = getUpdatedModels([2], modelList, range); + expect(updatedModelList).toEqual([{ value: 2, id: '1', isInvalid: false }]); + }); + + test('should update model list when number list has undefined value', () => { + const updatedModelList = getUpdatedModels([1, undefined], modelList, range); + modelList[1].value = ''; + modelList[1].isInvalid = true; + expect(updatedModelList).toEqual(modelList); + }); + + test('should update model list when number order is invalid', () => { + const updatedModelList = getUpdatedModels([1, 3, 2], modelList, range, 2); + expect(updatedModelList).toEqual([ + modelList[0], + { ...modelList[1], value: 3 }, + { value: 2, id: expect.any(String), isInvalid: true }, + ]); + }); + }); + + describe('validateOrder', () => { + test('should return true when order is valid', () => { + expect(validateOrder([1, 2])).toEqual({ + isValidOrder: true, + }); + }); + + test('should return true when a number is undefined', () => { + expect(validateOrder([1, undefined])).toEqual({ + isValidOrder: true, + }); + }); + + test('should return false when order is invalid', () => { + expect(validateOrder([2, 1])).toEqual({ + isValidOrder: false, + modelIndex: 1, + }); + }); + }); + + describe('hasInvalidValues', () => { + test('should return false when there are no invalid models', () => { + expect(hasInvalidValues(modelList)).toBeFalsy(); + }); + + test('should return true when there is an invalid model', () => { + modelList[1].isInvalid = true; + expect(hasInvalidValues(modelList)).toBeTruthy(); + }); + }); + + describe('parse', () => { + test('should return a number', () => { + expect(parse('3')).toBe(3); + }); + + test('should return an empty string when value is invalid', () => { + expect(parse('')).toBe(''); + expect(parse('test')).toBe(''); + expect(parse('NaN')).toBe(''); + }); + }); + + describe('validateValue', () => { + test('should return valid', () => { + expect(validateValue(3, range)).toEqual({ isInvalid: false }); + }); + + test('should return invalid', () => { + range.within = jest.fn(() => false); + expect(validateValue(11, range)).toEqual({ isInvalid: true, error: expect.any(String) }); + }); + }); + + describe('getNextModel', () => { + test('should return 3 as next value', () => { + expect(getNextModel(modelList, range)).toEqual({ + value: 3, + id: expect.any(String), + isInvalid: false, + }); + }); + + test('should return 1 as next value', () => { + expect(getNextModel([{ value: '', id: '2', isInvalid: false }], range)).toEqual({ + value: 1, + id: expect.any(String), + isInvalid: false, + }); + }); + + test('should return 9 as next value', () => { + expect(getNextModel([{ value: 11, id: '2', isInvalid: false }], range)).toEqual({ + value: 9, + id: expect.any(String), + isInvalid: false, + }); + }); + }); + + describe('getRange', () => { + test('should return default range', () => { + expect(getRange()).toEqual({ + min: 0, + max: Infinity, + maxInclusive: false, + minInclusive: true, + }); + }); + + test('should return parsed range', () => { + expect(getRange('(-Infinity, 100]')).toEqual({ + min: -Infinity, + max: 100, + maxInclusive: true, + minInclusive: false, + }); + }); + + test('should throw an error', () => { + expect(() => getRange('test')).toThrowError(); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts index f90f545aeffbaa..563e8f0a6a9b7f 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts @@ -27,6 +27,7 @@ import { NumberRowModel } from './number_row'; const EMPTY_STRING = ''; const defaultRange = parseRange('[0,Infinity)'); const generateId = htmlIdGenerator(); +const defaultModel = { value: 0, id: generateId(), isInvalid: false }; function parse(value: string) { const parsedValue = parseFloat(value); @@ -42,44 +43,37 @@ function getRange(range?: string): Range { } function validateValue(value: number | '', numberRange: Range) { - const result = { - isValid: true, - errors: [] as string[], + const result: { isInvalid: boolean; error?: string } = { + isInvalid: false, }; if (value === EMPTY_STRING) { - result.isValid = false; + result.isInvalid = true; } else if (!numberRange.within(value)) { - result.isValid = false; - result.errors.push( - i18n.translate('common.ui.aggTypes.numberList.invalidRangeErrorMessage', { - defaultMessage: 'The value should be in the range of {min} to {max}.', - values: { min: numberRange.min, max: numberRange.max }, - }) - ); + result.isInvalid = true; + result.error = i18n.translate('common.ui.aggTypes.numberList.invalidRangeErrorMessage', { + defaultMessage: 'The value should be in the range of {min} to {max}.', + values: { min: numberRange.min, max: numberRange.max }, + }); } return result; } -function validateOrder(list: NumberRowModel[]) { - let isInvalidOrder = false; - list.forEach((model, index, array) => { - const previousModel = array[index - 1]; - if (previousModel && model.value !== EMPTY_STRING) { - const isInvalidOrderOfItem = model.value <= previousModel.value; - - if (!model.isInvalid && isInvalidOrderOfItem) { - model.isInvalid = true; - } +function validateOrder(list: Array) { + const result: { isValidOrder: boolean; modelIndex?: number } = { + isValidOrder: true, + }; - if (isInvalidOrderOfItem) { - isInvalidOrder = true; - } + list.forEach((inputValue, index, array) => { + const previousModel = array[index - 1]; + if (previousModel !== undefined && inputValue !== undefined && inputValue <= previousModel) { + result.isValidOrder = false; + result.modelIndex = index; } }); - return isInvalidOrder; + return result; } function getNextModel(list: NumberRowModel[], range: Range): NumberRowModel { @@ -104,26 +98,27 @@ function getInitModelList(list: Array): NumberRowModel[] { id: generateId(), isInvalid: false, })) - : [{ value: 0, id: generateId(), isInvalid: false }]; + : [defaultModel]; } function getUpdatedModels( numberList: Array, modelList: NumberRowModel[], - numberRange: Range + numberRange: Range, + invalidOrderModelIndex?: number ): NumberRowModel[] { if (!numberList.length) { - return modelList; + return [defaultModel]; } return numberList.map((number, index) => { const model = modelList[index] || { id: generateId() }; const newValue: NumberRowModel['value'] = number === undefined ? EMPTY_STRING : number; - const { isValid, errors } = validateValue(newValue, numberRange); + const { isInvalid, error } = validateValue(newValue, numberRange); return { ...model, value: newValue, - isInvalid: !isValid, - errors, + isInvalid: invalidOrderModelIndex === index ? true : isInvalid, + error, }; }); } diff --git a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx index 2c0a2b6be37f8e..4ebe7b0d835d7c 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { Query, QueryBarInput } from 'plugins/data'; import { AggConfig } from '../../..'; import { npStart } from '../../../../new_platform'; -import { Storage } from '../../../../storage'; +import { Storage } from '../../../../../../../plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../../../../plugins/kibana_react/public'; const localStorage = new Storage(window.localStorage); @@ -94,7 +94,7 @@ function FilterRow({ { + const setModelValidity = (isListValid: boolean) => { setIsValid(isListValid); setValidity(isListValid); }; @@ -62,7 +62,7 @@ function PercentileRanksEditor({ showValidation={showValidation} onChange={setValue} setTouched={setTouched} - setValidity={setModelValidy} + setValidity={setModelValidity} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx b/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx index 158ae3442ff0c0..b8ad212edead9a 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx @@ -37,7 +37,7 @@ function PercentilesEditor({ }); const [isValid, setIsValid] = useState(true); - const setModelValidy = (isListValid: boolean) => { + const setModelValidity = (isListValid: boolean) => { setIsValid(isListValid); setValidity(isListValid); }; @@ -62,7 +62,7 @@ function PercentilesEditor({ showValidation={showValidation} onChange={setValue} setTouched={setTouched} - setValidity={setModelValidy} + setValidity={setModelValidity} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx index 836049eb95cabf..df1640273135e4 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx @@ -21,7 +21,7 @@ import React, { useEffect, useState } from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AggParamType } from '../../../../agg_types/param_types/agg'; -import { AggConfig } from '../../..'; +import { AggConfig } from '../../../../agg_types/agg_config'; import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..'; function SubMetricParamEditor({ diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js index af0ebc65680c39..43d2962df0a1e0 100644 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ b/src/legacy/ui/public/vis/editors/default/default.js @@ -34,10 +34,11 @@ import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipelin import { DefaultEditorSize } from '../../editor_size'; import { VisEditorTypesRegistryProvider } from '../../../registry/vis_editor_types'; -import { getVisualizeLoader } from '../../../visualize/loader/visualize_loader'; import { AggGroupNames } from './agg_groups'; -const defaultEditor = function ($rootScope, $compile) { +import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; + +const defaultEditor = function ($rootScope, $compile, getAppState) { return class DefaultEditor { static key = 'default'; @@ -57,7 +58,7 @@ const defaultEditor = function ($rootScope, $compile) { } } - render({ uiState, timeRange, filters, appState }) { + render({ uiState, timeRange, filters, query }) { let $scope; const updateScope = () => { @@ -66,7 +67,7 @@ const defaultEditor = function ($rootScope, $compile) { //$scope.$apply(); }; - return new Promise(resolve => { + return new Promise(async (resolve) => { if (!this.$scope) { this.$scope = $scope = $rootScope.$new(); @@ -157,23 +158,21 @@ const defaultEditor = function ($rootScope, $compile) { if (!this._handler) { const visualizationEl = this.el.find('.visEditor__canvas')[0]; - getVisualizeLoader().then(loader => { - if (!visualizationEl) { - return; - } - this._loader = loader; - this._handler = this._loader.embedVisualizationWithSavedObject(visualizationEl, this.savedObj, { - uiState: uiState, - listenOnChange: false, - timeRange: timeRange, - filters: filters, - appState: appState, - }); + + this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(this.savedObj, { + uiState: uiState, + appState: getAppState(), + timeRange: timeRange, + filters: filters || [], + query: query, }); + this._handler.render(visualizationEl); + } else { - this._handler.update({ + this._handler.updateInput({ timeRange: timeRange, - filters: filters, + filters: filters || [], + query: query, }); } diff --git a/src/legacy/ui/public/vis/vis_filters/brush_event.js b/src/legacy/ui/public/vis/vis_filters/brush_event.js index 90cbaf7c048ee2..17ab302a20cb16 100644 --- a/src/legacy/ui/public/vis/vis_filters/brush_event.js +++ b/src/legacy/ui/public/vis/vis_filters/brush_event.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import moment from 'moment'; -import { buildRangeFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../plugins/data/public'; export function onBrushEvent(event) { const isNumber = event.data.ordered; @@ -56,7 +56,7 @@ export function onBrushEvent(event) { }; } - const newFilter = buildRangeFilter( + const newFilter = esFilters.buildRangeFilter( field, range, indexPattern, diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index f19e2440a21e9b..e879d040125f11 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -20,8 +20,8 @@ import _ from 'lodash'; import { pushFilterBarFilters } from '../push_filters'; import { onBrushEvent } from './brush_event'; -import { uniqFilters } from '../../../../core_plugins/data/public'; -import { toggleFilterNegated } from '@kbn/es-query'; +import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; + /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -94,7 +94,7 @@ const createFiltersFromEvent = (event) => { if (filter) { filter.forEach(f => { if (event.negate) { - f = toggleFilterNegated(f); + f = esFilters.toggleFilterNegated(f); } filters.push(f); }); diff --git a/src/legacy/ui/public/vislib/_index.scss b/src/legacy/ui/public/vislib/_index.scss index 4e4ba8175444d6..0344fbb5359ece 100644 --- a/src/legacy/ui/public/vislib/_index.scss +++ b/src/legacy/ui/public/vislib/_index.scss @@ -3,3 +3,4 @@ @import './lib/index'; @import './visualizations/point_series/index'; +@import './visualizations/gauges/index'; diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss b/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss new file mode 100644 index 00000000000000..1c6b5e669a94b1 --- /dev/null +++ b/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss @@ -0,0 +1 @@ +@import './meter'; diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss b/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss new file mode 100644 index 00000000000000..971eaa0eab2f6a --- /dev/null +++ b/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss @@ -0,0 +1,3 @@ +.visGauge__meter--outline { + stroke: $euiBorderColor; +} diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js index b6a308271497a8..7533ae01bc4acd 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js +++ b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js @@ -38,6 +38,7 @@ const defaultConfig = { percentageMode: true, innerSpace: 5, extents: [0, 10000], + outline: false, scale: { show: true, color: '#666', @@ -248,11 +249,13 @@ export class MeterGauge { gauges .append('path') .attr('d', bgArc) + .attr('class', this.gaugeConfig.outline ? 'visGauge__meter--outline' : undefined) .style('fill', this.gaugeConfig.style.bgFill); const series = gauges .append('path') .attr('d', arc) + .attr('class', this.gaugeConfig.outline ? 'visGauge__meter--outline' : undefined) .style('fill', (d) => this.getColorBucket(Math.max(min, d.y))); const smallContainer = svg.node().getBBox().height < 70; diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts index bc2152911d1ecd..fb16e095b34187 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -22,13 +22,13 @@ import { debounce, forEach, get, isEqual } from 'lodash'; import * as Rx from 'rxjs'; import { share } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; -import { Filter } from '@kbn/es-query'; import { toastNotifications } from 'ui/notify'; // @ts-ignore untyped dependency import { AggConfigs } from 'ui/agg_types/agg_configs'; import { SearchSource } from 'ui/courier'; import { QueryFilter } from 'ui/filter_manager/query_filter'; -import { TimeRange } from 'src/plugins/data/public'; + +import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../plugins/data/public'; import { registries } from '../../../../core_plugins/interpreter/public/registries'; import { Inspector } from '../../inspector'; import { Adapters } from '../../inspector/types'; @@ -42,7 +42,8 @@ import { Vis } from '../../vis'; import { VisFiltersProvider } from '../../vis/vis_filters'; import { PipelineDataLoader } from './pipeline_data_loader'; import { visualizationLoader } from './visualization_loader'; -import { onlyDisabledFiltersChanged, Query } from '../../../../core_plugins/data/public'; +import { Query } from '../../../../core_plugins/data/public'; +import { esFilters } from '../../../../../plugins/data/public'; import { DataAdapter, RequestAdapter } from '../../inspector/adapters'; @@ -66,7 +67,7 @@ export interface RequestHandlerParams { aggs: AggConfigs; timeRange?: TimeRange; query?: Query; - filters?: Filter[]; + filters?: esFilters.Filter[]; forceFetch: boolean; queryFilter: QueryFilter; uiState?: PersistedState; @@ -537,16 +538,16 @@ export class EmbeddedVisualizeHandler { private rendererProvider = (response: VisResponseData | null) => { const renderer = registries.renderers.get(get(response || {}, 'as', 'visualization')); - const args = [ - this.element, - get(response, 'value', { visType: this.vis.type.name }), - this.handlers, - ]; if (!renderer) { return null; } - return () => renderer.render(...args); + return () => + renderer.render( + this.element, + get(response, 'value', { visType: this.vis.type.name }), + this.handlers + ); }; } diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts index 17ec224f4c7ded..21b13abea440e1 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts @@ -24,7 +24,7 @@ import { SearchSource } from 'ui/courier'; import { AggConfig, Vis, VisParams, VisState } from 'ui/vis'; import { isDateHistogramBucketAggConfig } from 'ui/agg_types/buckets/date_histogram'; import moment from 'moment'; -import { SerializedFieldFormat } from 'src/plugins/expressions/common/expressions/types/common'; +import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { createFormat } from './utilities'; interface SchemaConfigParams { diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index c12bd222663aed..c5ebc75973d0c4 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { identity } from 'lodash'; import { AggConfig, Vis } from 'ui/vis'; -import { SerializedFieldFormat } from 'src/plugins/expressions/common/expressions/types/common'; +import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { FieldFormat } from '../../../../../../plugins/data/common/field_formats'; diff --git a/src/legacy/ui/public/visualize/loader/types.ts b/src/legacy/ui/public/visualize/loader/types.ts index 87183d839e6373..525ec35834ecde 100644 --- a/src/legacy/ui/public/visualize/loader/types.ts +++ b/src/legacy/ui/public/visualize/loader/types.ts @@ -17,7 +17,6 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { TimeRange } from 'src/plugins/data/public'; import { Query } from 'src/legacy/core_plugins/data/public'; import { SavedObject } from 'ui/saved_objects/saved_object'; @@ -26,6 +25,7 @@ import { SearchSource } from '../../courier'; import { PersistedState } from '../../persisted_state'; import { AppState } from '../../state_management/app_state'; import { Vis } from '../../vis'; +import { esFilters } from '../../../../../plugins/data/public'; export interface VisSavedObject extends SavedObject { vis: Vis; @@ -68,7 +68,7 @@ export interface VisualizeLoaderParams { /** * Specifies the filters that should be applied to that visualization. */ - filters?: Filter[]; + filters?: esFilters.Filter[]; /** * The query that should apply to that visualization. */ diff --git a/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts b/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts index ec612f7dd03735..9f3aa190917d73 100644 --- a/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts +++ b/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts @@ -22,13 +22,13 @@ import { get } from 'lodash'; import { toastNotifications } from 'ui/notify'; import { AggConfig } from 'ui/vis'; -import { Filter } from '@kbn/es-query'; import { Query } from 'src/legacy/core_plugins/data/public'; import { timefilter } from 'ui/timefilter'; import { Vis } from '../../../vis'; +import { esFilters } from '../../../../../../plugins/data/public'; interface QueryGeohashBoundsParams { - filters?: Filter[]; + filters?: esFilters.Filter[]; query?: Query; } @@ -76,7 +76,7 @@ export async function queryGeohashBounds(vis: Vis, params: QueryGeohashBoundsPar const useTimeFilter = !!indexPattern.timeFieldName; if (useTimeFilter) { const filter = timefilter.createFilter(indexPattern); - if (filter) activeFilters.push((filter as any) as Filter); + if (filter) activeFilters.push((filter as any) as esFilters.Filter); } return activeFilters; }); diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 883358e0d3c9aa..0d05ea259d1a13 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -185,7 +185,7 @@ export function uiRenderMixin(kbnServer, server, config) { async function getUiSettings({ request, includeUserProvidedConfig }) { const uiSettings = request.getUiSettingsService(); return props({ - defaults: uiSettings.getDefaults(), + defaults: uiSettings.getRegistered(), user: includeUserProvidedConfig && uiSettings.getUserProvided() }); } diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts index 9f553b37935d75..dd3f12903abcac 100644 --- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts +++ b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts @@ -20,7 +20,7 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory'; import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request'; // @ts-ignore @@ -73,7 +73,7 @@ describe('uiSettingsMixin()', () => { newPlatform: { __internals: { uiSettings: { - setDefaults: sinon.stub(), + register: sinon.stub(), }, }, }, @@ -93,9 +93,9 @@ describe('uiSettingsMixin()', () => { it('passes uiSettingsDefaults to the new platform', () => { const { kbnServer } = setup(); - sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.setDefaults); + sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.register); sinon.assert.calledWithExactly( - kbnServer.newPlatform.__internals.uiSettings.setDefaults, + kbnServer.newPlatform.__internals.uiSettings.register, uiSettingDefaults ); }); @@ -129,7 +129,7 @@ describe('uiSettingsMixin()', () => { sinon.assert.notCalled(uiSettingsServiceFactoryStub); - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); decorations.server.uiSettingsServiceFactory({ savedObjectsClient, }); diff --git a/src/legacy/ui/ui_settings/routes/delete.ts b/src/legacy/ui/ui_settings/routes/delete.ts deleted file mode 100644 index 7825204e6b99b1..00000000000000 --- a/src/legacy/ui/ui_settings/routes/delete.ts +++ /dev/null @@ -1,37 +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 { Legacy } from 'kibana'; - -async function handleRequest(request: Legacy.Request) { - const { key } = request.params; - const uiSettings = request.getUiSettingsService(); - - await uiSettings.remove(key); - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const deleteRoute = { - path: '/api/kibana/settings/{key}', - method: 'DELETE', - handler: async (request: Legacy.Request) => { - return await handleRequest(request); - }, -}; diff --git a/src/legacy/ui/ui_settings/routes/get.ts b/src/legacy/ui/ui_settings/routes/get.ts deleted file mode 100644 index 3e165a12522bbe..00000000000000 --- a/src/legacy/ui/ui_settings/routes/get.ts +++ /dev/null @@ -1,34 +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 { Legacy } from 'kibana'; - -async function handleRequest(request: Legacy.Request) { - const uiSettings = request.getUiSettingsService(); - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const getRoute = { - path: '/api/kibana/settings', - method: 'GET', - handler(request: Legacy.Request) { - return handleRequest(request); - }, -}; diff --git a/src/legacy/ui/ui_settings/routes/index.ts b/src/legacy/ui/ui_settings/routes/index.ts deleted file mode 100644 index f3c9d4f0d8d14f..00000000000000 --- a/src/legacy/ui/ui_settings/routes/index.ts +++ /dev/null @@ -1,23 +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. - */ - -export { deleteRoute } from './delete'; -export { getRoute } from './get'; -export { setManyRoute } from './set_many'; -export { setRoute } from './set'; diff --git a/src/legacy/ui/ui_settings/routes/set.ts b/src/legacy/ui/ui_settings/routes/set.ts deleted file mode 100644 index 1f1ab17a0daf77..00000000000000 --- a/src/legacy/ui/ui_settings/routes/set.ts +++ /dev/null @@ -1,55 +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 { Legacy } from 'kibana'; -import Joi from 'joi'; - -async function handleRequest(request: Legacy.Request) { - const { key } = request.params; - const { value } = request.payload as any; - const uiSettings = request.getUiSettingsService(); - - await uiSettings.set(key, value); - - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const setRoute = { - path: '/api/kibana/settings/{key}', - method: 'POST', - config: { - validate: { - params: Joi.object() - .keys({ - key: Joi.string().required(), - }) - .default(), - - payload: Joi.object() - .keys({ - value: Joi.any().required(), - }) - .required(), - }, - handler(request: Legacy.Request) { - return handleRequest(request); - }, - }, -}; diff --git a/src/legacy/ui/ui_settings/routes/set_many.ts b/src/legacy/ui/ui_settings/routes/set_many.ts deleted file mode 100644 index 18b1046417feca..00000000000000 --- a/src/legacy/ui/ui_settings/routes/set_many.ts +++ /dev/null @@ -1,50 +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 { Legacy } from 'kibana'; -import Joi from 'joi'; - -async function handleRequest(request: Legacy.Request) { - const { changes } = request.payload as any; - const uiSettings = request.getUiSettingsService(); - - await uiSettings.setMany(changes); - - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const setManyRoute = { - path: '/api/kibana/settings', - method: 'POST', - config: { - validate: { - payload: Joi.object() - .keys({ - changes: Joi.object() - .unknown(true) - .required(), - }) - .required(), - }, - handler(request: Legacy.Request) { - return handleRequest(request); - }, - }, -}; diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js index 8c7ef25c6f8d70..64251d290776c8 100644 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ b/src/legacy/ui/ui_settings/ui_settings_mixin.js @@ -19,12 +19,6 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory'; import { getUiSettingsServiceForRequest } from './ui_settings_service_for_request'; -import { - deleteRoute, - getRoute, - setManyRoute, - setRoute, -} from './routes'; export function uiSettingsMixin(kbnServer, server) { const { uiSettingDefaults = {} } = kbnServer.uiExports; @@ -43,7 +37,7 @@ export function uiSettingsMixin(kbnServer, server) { return acc; }, {}); - kbnServer.newPlatform.__internals.uiSettings.setDefaults(mergedUiSettingDefaults); + kbnServer.newPlatform.__internals.uiSettings.register(mergedUiSettingDefaults); server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => { return uiSettingsServiceFactory(server, options); @@ -58,9 +52,4 @@ export function uiSettingsMixin(kbnServer, server) { server.uiSettings has been removed, see https://github.com/elastic/kibana/pull/12243. `); }); - - server.route(deleteRoute); - server.route(getRoute); - server.route(setManyRoute); - server.route(setRoute); } diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index 5b9dd2d04a550a..3f3bb3e4e196c7 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -25,13 +25,12 @@ import fs from 'fs'; import webpack from 'webpack'; import { promisify } from 'util'; import path from 'path'; -import rimraf from 'rimraf'; +import del from 'del'; const readFileAsync = promisify(fs.readFile); const mkdirAsync = promisify(fs.mkdir); const existsAsync = promisify(fs.exists); const writeFileAsync = promisify(fs.writeFile); -const rimrafAsync = promisify(rimraf); export class DllCompiler { static getRawDllConfig(uiBundles = {}, babelLoaderCacheDir = '', threadLoaderPoolConfig = {}) { @@ -267,7 +266,7 @@ export class DllCompiler { // Delete the built dll, as it contains invalid modules, and reject listing // all the not allowed modules try { - await rimrafAsync(this.rawDllConfig.outputPath); + await del(this.rawDllConfig.outputPath); } catch (e) { return reject(e); } diff --git a/src/plugins/dashboard_embeddable_container/kibana.json b/src/plugins/dashboard_embeddable_container/kibana.json new file mode 100644 index 00000000000000..aab23316f606c0 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/kibana.json @@ -0,0 +1,11 @@ +{ + "id": "dashboard_embeddable_container", + "version": "kibana", + "requiredPlugins": [ + "embeddable", + "inspector", + "uiActions" + ], + "server": false, + "ui": true +} diff --git a/src/plugins/dashboard_embeddable_container/public/_index.scss b/src/plugins/dashboard_embeddable_container/public/_index.scss new file mode 100644 index 00000000000000..ccb8299072fb76 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/_index.scss @@ -0,0 +1,5 @@ +@import '../../embeddable/public/variables'; + +@import './embeddable/grid/index'; +@import './embeddable/panel/index'; +@import './embeddable/viewport/index'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.test.tsx b/src/plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx similarity index 97% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.test.tsx rename to src/plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx index 0fa34817bee862..f8c05170e8f672 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_api'; +import { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_plugin'; import { ExpandPanelAction } from './expand_panel_action'; import { DashboardContainer } from '../embeddable'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; @@ -27,7 +27,7 @@ import { ContactCardEmbeddable, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, -} from '../../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; +} from '../embeddable_plugin_test_samples'; import { DashboardOptions } from '../embeddable/dashboard_container_factory'; const embeddableFactories = new Map(); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx b/src/plugins/dashboard_embeddable_container/public/actions/expand_panel_action.tsx similarity index 93% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx rename to src/plugins/dashboard_embeddable_container/public/actions/expand_panel_action.tsx index f05d0b0efc2ee1..68f68f8a53bccd 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx +++ b/src/plugins/dashboard_embeddable_container/public/actions/expand_panel_action.tsx @@ -18,12 +18,9 @@ */ import { i18n } from '@kbn/i18n'; -import { IEmbeddable } from '../../../../../../embeddable_api/public/np_ready/public'; +import { IEmbeddable } from '../embeddable_plugin'; +import { IAction, IncompatibleActionError } from '../ui_actions_plugin'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; -import { - IAction, - IncompatibleActionError, -} from '../../../../../../../../../src/plugins/ui_actions/public'; export const EXPAND_PANEL_ACTION = 'togglePanel'; diff --git a/src/plugins/dashboard_embeddable_container/public/actions/index.ts b/src/plugins/dashboard_embeddable_container/public/actions/index.ts new file mode 100644 index 00000000000000..6c0db82fbbc5bb --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/actions/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 { ExpandPanelAction, EXPAND_PANEL_ACTION } from './expand_panel_action'; +export { ReplacePanelAction, REPLACE_PANEL_ACTION } from './replace_panel_action'; diff --git a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx new file mode 100644 index 00000000000000..cb354375e7a683 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx @@ -0,0 +1,63 @@ +/* + * 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 { CoreStart } from '../../../../core/public'; +import { ReplacePanelFlyout } from './replace_panel_flyout'; +import { + IEmbeddable, + EmbeddableInput, + EmbeddableOutput, + Start as EmbeddableStart, + IContainer, +} from '../embeddable_plugin'; + +export async function openReplacePanelFlyout(options: { + embeddable: IContainer; + core: CoreStart; + savedObjectFinder: React.ComponentType; + notifications: CoreStart['notifications']; + panelToRemove: IEmbeddable; + getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; +}) { + const { + embeddable, + core, + panelToRemove, + savedObjectFinder, + notifications, + getEmbeddableFactories, + } = options; + const flyoutSession = core.overlays.openFlyout( + { + if (flyoutSession) { + flyoutSession.close(); + } + }} + panelToRemove={panelToRemove} + savedObjectsFinder={savedObjectFinder} + notifications={notifications} + getEmbeddableFactories={getEmbeddableFactories} + />, + { + 'data-test-subj': 'replacePanelFlyout', + } + ); +} diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx new file mode 100644 index 00000000000000..de29e1dec85a86 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx @@ -0,0 +1,155 @@ +/* + * 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 { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_plugin'; +import { ReplacePanelAction } from './replace_panel_action'; +import { DashboardContainer } from '../embeddable'; +import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; +import { + CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, + ContactCardEmbeddable, + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, +} from '../embeddable_plugin_test_samples'; +import { DashboardOptions } from '../embeddable/dashboard_container_factory'; + +const embeddableFactories = new Map(); +embeddableFactories.set( + CONTACT_CARD_EMBEDDABLE, + new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) +); +const getEmbeddableFactories = () => embeddableFactories.values(); + +let container: DashboardContainer; +let embeddable: ContactCardEmbeddable; + +beforeEach(async () => { + const options: DashboardOptions = { + ExitFullScreenButton: () => null, + SavedObjectFinder: () => null, + application: {} as any, + embeddable: { + getEmbeddableFactory: (id: string) => embeddableFactories.get(id)!, + } as any, + inspector: {} as any, + notifications: {} as any, + overlays: {} as any, + savedObjectMetaData: {} as any, + uiActions: {} as any, + }; + const input = getSampleDashboardInput({ + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, + }); + container = new DashboardContainer(input, options); + + const contactCardEmbeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Kibana', + }); + + if (isErrorEmbeddable(contactCardEmbeddable)) { + throw new Error('Failed to create embeddable'); + } else { + embeddable = contactCardEmbeddable; + } +}); + +test('Executes the replace panel action', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction( + core, + SavedObjectFinder, + notifications, + getEmbeddableFactories + ); + action.execute({ embeddable }); +}); + +test('Is not compatible when embeddable is not in a dashboard container', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction( + core, + SavedObjectFinder, + notifications, + getEmbeddableFactories + ); + expect( + await action.isCompatible({ + embeddable: new ContactCardEmbeddable( + { firstName: 'sue', id: '123' }, + { execAction: (() => null) as any } + ), + }) + ).toBe(false); +}); + +test('Execute throws an error when called with an embeddable not in a parent', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction( + core, + SavedObjectFinder, + notifications, + getEmbeddableFactories + ); + async function check() { + await action.execute({ embeddable: container }); + } + await expect(check()).rejects.toThrow(Error); +}); + +test('Returns title', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction( + core, + SavedObjectFinder, + notifications, + getEmbeddableFactories + ); + expect(action.getDisplayName({ embeddable })).toBeDefined(); +}); + +test('Returns an icon', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction( + core, + SavedObjectFinder, + notifications, + getEmbeddableFactories + ); + expect(action.getIconType({ embeddable })).toBeDefined(); +}); diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx new file mode 100644 index 00000000000000..f6d2fcbcd57fd4 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx @@ -0,0 +1,91 @@ +/* + * 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 { CoreStart } from '../../../../core/public'; +import { IEmbeddable, ViewMode, Start as EmbeddableStart } from '../embeddable_plugin'; +import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; +import { IAction, IncompatibleActionError } from '../ui_actions_plugin'; +import { openReplacePanelFlyout } from './open_replace_panel_flyout'; + +export const REPLACE_PANEL_ACTION = 'replacePanel'; + +function isDashboard(embeddable: IEmbeddable): embeddable is DashboardContainer { + return embeddable.type === DASHBOARD_CONTAINER_TYPE; +} + +interface ActionContext { + embeddable: IEmbeddable; +} + +export class ReplacePanelAction implements IAction { + public readonly type = REPLACE_PANEL_ACTION; + public readonly id = REPLACE_PANEL_ACTION; + public order = 11; + + constructor( + private core: CoreStart, + private savedobjectfinder: React.ComponentType, + private notifications: CoreStart['notifications'], + private getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'] + ) {} + + public getDisplayName({ embeddable }: ActionContext) { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + return i18n.translate('dashboardEmbeddableContainer.panel.removePanel.replacePanel', { + defaultMessage: 'Replace panel', + }); + } + + public getIconType({ embeddable }: ActionContext) { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + return 'kqlOperand'; + } + + public async isCompatible({ embeddable }: ActionContext) { + if (embeddable.getInput().viewMode) { + if (embeddable.getInput().viewMode === ViewMode.VIEW) { + return false; + } + } + + return Boolean(embeddable.parent && isDashboard(embeddable.parent)); + } + + public async execute({ embeddable }: ActionContext) { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + + const view = embeddable; + const dash = embeddable.parent; + openReplacePanelFlyout({ + embeddable: dash, + core: this.core, + savedObjectFinder: this.savedobjectfinder, + notifications: this.notifications, + panelToRemove: view, + getEmbeddableFactories: this.getEmbeddableFactories, + }); + } +} diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx new file mode 100644 index 00000000000000..02e5f45fae3bd6 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx @@ -0,0 +1,143 @@ +/* + * 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 React from 'react'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiGlobalToastListToast as Toast, +} from '@elastic/eui'; +import { DashboardPanelState } from '../embeddable'; +import { NotificationsStart } from '../../../../core/public'; +import { + IContainer, + IEmbeddable, + EmbeddableInput, + EmbeddableOutput, + Start as EmbeddableStart, +} from '../embeddable_plugin'; + +interface Props { + container: IContainer; + savedObjectsFinder: React.ComponentType; + onClose: () => void; + notifications: NotificationsStart; + panelToRemove: IEmbeddable; + getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; +} + +export class ReplacePanelFlyout extends React.Component { + private lastToast: Toast = { + id: 'panelReplaceToast', + }; + + constructor(props: Props) { + super(props); + } + + public showToast = (name: string) => { + // To avoid the clutter of having toast messages cover flyout + // close previous toast message before creating a new one + if (this.lastToast) { + this.props.notifications.toasts.remove(this.lastToast); + } + + this.lastToast = this.props.notifications.toasts.addSuccess({ + title: i18n.translate( + 'dashboardEmbeddableContainer.addPanel.savedObjectAddedToContainerSuccessMessageTitle', + { + defaultMessage: '{savedObjectName} was added', + values: { + savedObjectName: name, + }, + } + ), + 'data-test-subj': 'addObjectToContainerSuccess', + }); + }; + + public onReplacePanel = async (id: string, type: string, name: string) => { + const originalPanels = this.props.container.getInput().panels; + const filteredPanels = { ...originalPanels }; + + const nnw = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.w; + const nnh = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.h; + const nnx = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.x; + const nny = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.y; + + // add the new view + const newObj = await this.props.container.addSavedObjectEmbeddable(type, id); + + const finalPanels = this.props.container.getInput().panels; + (finalPanels[newObj.id] as DashboardPanelState).gridData.w = nnw; + (finalPanels[newObj.id] as DashboardPanelState).gridData.h = nnh; + (finalPanels[newObj.id] as DashboardPanelState).gridData.x = nnx; + (finalPanels[newObj.id] as DashboardPanelState).gridData.y = nny; + + // delete the old view + delete finalPanels[this.props.panelToRemove.id]; + + // apply changes + this.props.container.updateInput(finalPanels); + this.props.container.reload(); + + this.showToast(name); + this.props.onClose(); + }; + + public render() { + const SavedObjectFinder = this.props.savedObjectsFinder; + const savedObjectsFinder = ( + + Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType + ) + .map(({ savedObjectMetaData }) => savedObjectMetaData as any)} + showFilter={true} + onChoose={this.onReplacePanel} + /> + ); + + const panelToReplace = 'Replace panel ' + this.props.panelToRemove.getTitle() + ' with:'; + + return ( + + + +

+ {panelToReplace} +

+
+
+ {savedObjectsFinder} +
+ ); + } +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_constants.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_constants.ts similarity index 94% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_constants.ts rename to src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_constants.ts index 941ddd3c5efecf..34cd8d42a11880 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_constants.ts +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_constants.ts @@ -21,3 +21,4 @@ export const DASHBOARD_GRID_COLUMN_COUNT = 48; export const DASHBOARD_GRID_HEIGHT = 20; export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; export const DEFAULT_PANEL_HEIGHT = 15; +export const DASHBOARD_CONTAINER_TYPE = 'dashboard'; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.test.tsx new file mode 100644 index 00000000000000..770c46c62e42f6 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.test.tsx @@ -0,0 +1,141 @@ +/* + * 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. + */ + +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; +import { nextTick } from 'test_utils/enzyme_helpers'; +import { isErrorEmbeddable, ViewMode, EmbeddableFactory } from '../embeddable_plugin'; +import { DashboardContainer, DashboardContainerOptions } from './dashboard_container'; +import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; +import { + CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, + ContactCardEmbeddableInput, + ContactCardEmbeddable, + ContactCardEmbeddableOutput, +} from '../embeddable_plugin_test_samples'; + +const options: DashboardContainerOptions = { + application: {} as any, + embeddable: { + getTriggerCompatibleActions: (() => []) as any, + getEmbeddableFactories: (() => []) as any, + getEmbeddableFactory: undefined as any, + } as any, + notifications: {} as any, + overlays: {} as any, + inspector: {} as any, + SavedObjectFinder: () => null, + ExitFullScreenButton: () => null, + uiActions: {} as any, +}; + +beforeEach(() => { + const embeddableFactories = new Map(); + embeddableFactories.set( + CONTACT_CARD_EMBEDDABLE, + new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) + ); + options.embeddable.getEmbeddableFactory = (id: string) => embeddableFactories.get(id) as any; +}); + +test('DashboardContainer initializes embeddables', async done => { + const initialInput = getSampleDashboardInput({ + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, + }); + const container = new DashboardContainer(initialInput, options); + + const subscription = container.getOutput$().subscribe(output => { + if (container.getOutput().embeddableLoaded['123']) { + const embeddable = container.getChild('123'); + expect(embeddable).toBeDefined(); + expect(embeddable.id).toBe('123'); + done(); + } + }); + + if (container.getOutput().embeddableLoaded['123']) { + const embeddable = container.getChild('123'); + expect(embeddable).toBeDefined(); + expect(embeddable.id).toBe('123'); + subscription.unsubscribe(); + done(); + } +}); + +test('DashboardContainer.addNewEmbeddable', async () => { + const container = new DashboardContainer(getSampleDashboardInput(), options); + const embeddable = await container.addNewEmbeddable( + CONTACT_CARD_EMBEDDABLE, + { + firstName: 'Kibana', + } + ); + expect(embeddable).toBeDefined(); + + if (!isErrorEmbeddable(embeddable)) { + expect(embeddable.getInput().firstName).toBe('Kibana'); + } else { + expect(false).toBe(true); + } + + const embeddableInContainer = container.getChild(embeddable.id); + expect(embeddableInContainer).toBeDefined(); + expect(embeddableInContainer.id).toBe(embeddable.id); +}); + +test('Container view mode change propagates to existing children', async () => { + const initialInput = getSampleDashboardInput({ + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, + }); + const container = new DashboardContainer(initialInput, options); + await nextTick(); + + const embeddable = await container.getChild('123'); + expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); + container.updateInput({ viewMode: ViewMode.EDIT }); + expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); +}); + +test('Container view mode change propagates to new children', async () => { + const container = new DashboardContainer(getSampleDashboardInput(), options); + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Bob', + }); + + expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); + + container.updateInput({ viewMode: ViewMode.EDIT }); + + expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); +}); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx similarity index 83% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx rename to src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx index 919995f544960d..8b258f35584388 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx @@ -20,9 +20,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { Filter } from '@kbn/es-query'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -import { IUiActionsStart } from '../../../../../../../../plugins/ui_actions/public'; +import { RefreshInterval, TimeRange, Query } from '../../../data/public'; +import { CoreStart } from '../../../../core/public'; +import { IUiActionsStart } from '../ui_actions_plugin'; import { Container, ContainerInput, @@ -30,24 +30,23 @@ import { ViewMode, EmbeddableFactory, IEmbeddable, -} from '../../../../../../embeddable_api/public/np_ready/public'; -import { DASHBOARD_CONTAINER_TYPE } from './dashboard_container_factory'; + Start as EmbeddableStartContract, +} from '../embeddable_plugin'; +import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; import { createPanelState } from './panel'; import { DashboardPanelState } from './types'; import { DashboardViewport } from './viewport/dashboard_viewport'; -import { Query } from '../../../../../../data/public'; -import { CoreStart } from '../../../../../../../../core/public'; -import { Start as InspectorStartContract } from '../../../../../../../../plugins/inspector/public'; -import { Start as EmbeddableStartContract } from '../../../../../../embeddable_api/public/np_ready/public'; +import { Start as InspectorStartContract } from '../../../inspector/public'; +import { esFilters } from '../../../../plugins/data/public'; import { + KibanaContextProvider, KibanaReactContext, KibanaReactContextValue, - KibanaContextProvider, -} from '../../../../../../../../plugins/kibana_react/public'; +} from '../../../kibana_react/public'; export interface DashboardContainerInput extends ContainerInput { viewMode: ViewMode; - filters: Filter[]; + filters: esFilters.Filter[]; query: Query; timeRange: TimeRange; refreshConfig?: RefreshInterval; @@ -66,7 +65,7 @@ interface IndexSignature { } export interface InheritedChildInput extends IndexSignature { - filters: Filter[]; + filters: esFilters.Filter[]; query: Query; timeRange: TimeRange; refreshConfig?: RefreshInterval; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container_factory.tsx new file mode 100644 index 00000000000000..c8a2837fd77d0b --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container_factory.tsx @@ -0,0 +1,87 @@ +/* + * 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 { SavedObjectAttributes } from '../../../../core/public'; +import { SavedObjectMetaData } from '../types'; +import { + ContainerOutput, + EmbeddableFactory, + ErrorEmbeddable, + Container, +} from '../embeddable_plugin'; +import { + DashboardContainer, + DashboardContainerInput, + DashboardContainerOptions, +} from './dashboard_container'; +import { DashboardCapabilities } from '../types'; +import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; + +export interface DashboardOptions extends DashboardContainerOptions { + savedObjectMetaData?: SavedObjectMetaData; +} + +export class DashboardContainerFactory extends EmbeddableFactory< + DashboardContainerInput, + ContainerOutput +> { + public readonly isContainerType = true; + public readonly type = DASHBOARD_CONTAINER_TYPE; + + private readonly allowEditing: boolean; + + constructor(private readonly options: DashboardOptions) { + super({ savedObjectMetaData: options.savedObjectMetaData }); + + const capabilities = (options.application.capabilities + .dashboard as unknown) as DashboardCapabilities; + + if (!capabilities || typeof capabilities !== 'object') { + throw new TypeError('Dashboard capabilities not found.'); + } + + this.allowEditing = !!capabilities.createNew && !!capabilities.showWriteControls; + } + + public isEditable() { + return this.allowEditing; + } + + public getDisplayName() { + return i18n.translate('dashboardEmbeddableContainer.factory.displayName', { + defaultMessage: 'dashboard', + }); + } + + public getDefaultInput(): Partial { + return { + panels: {}, + isFullScreenMode: false, + useMargins: true, + }; + } + + public async create( + initialInput: DashboardContainerInput, + parent?: Container + ): Promise { + return new DashboardContainer(initialInput, this.options, parent); + } +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/_dashboard_grid.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/_dashboard_grid.scss rename to src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/_index.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_index.scss similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/_index.scss rename to src/plugins/dashboard_embeddable_container/public/embeddable/grid/_index.scss diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx similarity index 96% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/dashboard_grid.test.tsx rename to src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx index 386aae9ddcf78e..e4338dc89153d3 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx @@ -23,15 +23,15 @@ import sizeMe from 'react-sizeme'; import React from 'react'; import { nextTick, mountWithIntl } from 'test_utils/enzyme_helpers'; import { skip } from 'rxjs/operators'; -import { EmbeddableFactory, GetEmbeddableFactory } from '../../embeddable_api'; +import { EmbeddableFactory, GetEmbeddableFactory } from '../../embeddable_plugin'; import { DashboardGrid, DashboardGridProps } from './dashboard_grid'; import { DashboardContainer, DashboardContainerOptions } from '../dashboard_container'; import { getSampleDashboardInput } from '../../test_helpers'; import { CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableFactory, -} from '../../../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; -import { KibanaContextProvider } from '../../../../../../../../../plugins/kibana_react/public'; +} from '../../embeddable_plugin_test_samples'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; let dashboardContainer: DashboardContainer | undefined; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.tsx similarity index 95% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/dashboard_grid.tsx rename to src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.tsx index 291bef15641f36..40db43427339d9 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.tsx @@ -20,19 +20,21 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; +// @ts-ignore +import sizeMe from 'react-sizeme'; + import { injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import _ from 'lodash'; import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; -// @ts-ignore -import sizeMe from 'react-sizeme'; -import { ViewMode, EmbeddableChildPanel } from '../../embeddable_api'; +import { ViewMode, EmbeddableChildPanel } from '../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; -import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; import { DashboardPanelState, GridData } from '../types'; -import { withKibana } from '../../../../../../../../../plugins/kibana_react/public'; +import { withKibana } from '../../../../kibana_react/public'; +import { DashboardContainerInput } from '../dashboard_container'; +import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; let lastValidGridSize = 0; @@ -174,16 +176,18 @@ class DashboardGridUi extends React.Component { isLayoutInvalid, }); - this.subscription = this.props.container.getInput$().subscribe(input => { - if (this.mounted) { - this.setState({ - panels: input.panels, - viewMode: input.viewMode, - useMargins: input.useMargins, - expandedPanelId: input.expandedPanelId, - }); - } - }); + this.subscription = this.props.container + .getInput$() + .subscribe((input: DashboardContainerInput) => { + if (this.mounted) { + this.setState({ + panels: input.panels, + viewMode: input.viewMode, + useMargins: input.useMargins, + expandedPanelId: input.expandedPanelId, + }); + } + }); } public componentWillUnmount() { diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/index.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/index.ts similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/grid/index.ts rename to src/plugins/dashboard_embeddable_container/public/embeddable/grid/index.ts diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/index.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/index.ts new file mode 100644 index 00000000000000..58bfd5eedefcb1 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/index.ts @@ -0,0 +1,31 @@ +/* + * 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 { DashboardContainerFactory } from './dashboard_container_factory'; +export { DashboardContainer, DashboardContainerInput } from './dashboard_container'; +export { createPanelState } from './panel'; + +export { DashboardPanelState, GridData } from './types'; + +export { + DASHBOARD_GRID_COLUMN_COUNT, + DEFAULT_PANEL_HEIGHT, + DEFAULT_PANEL_WIDTH, + DASHBOARD_CONTAINER_TYPE, +} from './dashboard_constants'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/_dashboard_panel.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/_dashboard_panel.scss similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/_dashboard_panel.scss rename to src/plugins/dashboard_embeddable_container/public/embeddable/panel/_dashboard_panel.scss diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/_index.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/_index.scss similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/_index.scss rename to src/plugins/dashboard_embeddable_container/public/embeddable/panel/_index.scss diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/create_panel_state.test.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/create_panel_state.test.ts similarity index 93% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/create_panel_state.test.ts rename to src/plugins/dashboard_embeddable_container/public/embeddable/panel/create_panel_state.test.ts index e66a25831577c6..8889f4dc275447 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/create_panel_state.test.ts +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/create_panel_state.test.ts @@ -20,9 +20,8 @@ import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; import { createPanelState } from './create_panel_state'; -import { EmbeddableInput } from '../../embeddable_api'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { CONTACT_CARD_EMBEDDABLE } from '../../../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; +import { EmbeddableInput } from '../../embeddable_plugin'; +import { CONTACT_CARD_EMBEDDABLE } from '../../embeddable_plugin_test_samples'; interface TestInput extends EmbeddableInput { test: string; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/create_panel_state.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/create_panel_state.ts similarity index 98% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/create_panel_state.ts rename to src/plugins/dashboard_embeddable_container/public/embeddable/panel/create_panel_state.ts index 4f3de4a9b2bfb8..7139cddf02b336 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/create_panel_state.ts +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/create_panel_state.ts @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { PanelState, EmbeddableInput } from '../../embeddable_api'; +import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DEFAULT_PANEL_HEIGHT, diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/index.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/panel/index.ts similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/panel/index.ts rename to src/plugins/dashboard_embeddable_container/public/embeddable/panel/index.ts diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/types.ts b/src/plugins/dashboard_embeddable_container/public/embeddable/types.ts new file mode 100644 index 00000000000000..480d03552ca68e --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/types.ts @@ -0,0 +1,34 @@ +/* + * 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 { PanelState, EmbeddableInput } from '../embeddable_plugin'; +export type PanelId = string; +export type SavedObjectId = string; + +export interface GridData { + w: number; + h: number; + x: number; + y: number; + i: string; +} + +export interface DashboardPanelState + extends PanelState { + readonly gridData: GridData; +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/_dashboard_viewport.scss rename to src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/_index.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_index.scss similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/_index.scss rename to src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_index.scss diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx similarity index 95% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/dashboard_viewport.test.tsx rename to src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx index 01bde21f91d3b7..7b83407bf8063c 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx @@ -24,15 +24,15 @@ import { skip } from 'rxjs/operators'; import { mount } from 'enzyme'; import { I18nProvider } from '@kbn/i18n/react'; import { nextTick } from 'test_utils/enzyme_helpers'; -import { EmbeddableFactory } from '../../embeddable_api'; +import { EmbeddableFactory } from '../../embeddable_plugin'; import { DashboardViewport, DashboardViewportProps } from './dashboard_viewport'; import { DashboardContainer, DashboardContainerOptions } from '../dashboard_container'; import { getSampleDashboardInput } from '../../test_helpers'; import { CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableFactory, -} from '../../../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; -import { KibanaContextProvider } from '../../../../../../../../../plugins/kibana_react/public'; +} from '../../embeddable_plugin_test_samples'; +import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; let dashboardContainer: DashboardContainer | undefined; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx similarity index 95% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/dashboard_viewport.tsx rename to src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx index 59accb225238be..13407e5e33725a 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx @@ -19,10 +19,10 @@ import React from 'react'; import { Subscription } from 'rxjs'; -import { PanelState } from '../../embeddable_api'; +import { PanelState } from '../../embeddable_plugin'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; import { DashboardGrid } from '../grid'; -import { context } from '../../../../../../../../../plugins/kibana_react/public'; +import { context } from '../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable_plugin.ts b/src/plugins/dashboard_embeddable_container/public/embeddable_plugin.ts new file mode 100644 index 00000000000000..30c0ec49751416 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/embeddable_plugin.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 '../../../plugins/embeddable/public'; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable_plugin_test_samples.ts b/src/plugins/dashboard_embeddable_container/public/embeddable_plugin_test_samples.ts new file mode 100644 index 00000000000000..0e49a94278dfc9 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/embeddable_plugin_test_samples.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. + */ + +// eslint-disable-next-line +export * from '../../../plugins/embeddable/public/lib/test_samples'; diff --git a/src/plugins/dashboard_embeddable_container/public/index.ts b/src/plugins/dashboard_embeddable_container/public/index.ts new file mode 100644 index 00000000000000..73597525105dba --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/index.ts @@ -0,0 +1,31 @@ +/* + * 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 { PluginInitializerContext } from '../../../core/public'; +import { DashboardEmbeddableContainerPublicPlugin } from './plugin'; + +export * from './types'; +export * from './actions'; +export * from './embeddable'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new DashboardEmbeddableContainerPublicPlugin(initializerContext); +} + +export { DashboardEmbeddableContainerPublicPlugin as Plugin }; diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx new file mode 100644 index 00000000000000..eadf70a36416a6 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx @@ -0,0 +1,110 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +import * as React from 'react'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { IUiActionsSetup, IUiActionsStart } from '../../../plugins/ui_actions/public'; +import { CONTEXT_MENU_TRIGGER, Plugin as EmbeddablePlugin } from './embeddable_plugin'; +import { ExpandPanelAction, ReplacePanelAction } from '.'; +import { DashboardContainerFactory } from './embeddable/dashboard_container_factory'; +import { Start as InspectorStartContract } from '../../../plugins/inspector/public'; +import { + SavedObjectFinder as SavedObjectFinderUi, + SavedObjectFinderProps, + ExitFullScreenButton as ExitFullScreenButtonUi, + ExitFullScreenButtonProps, +} from '../../../plugins/kibana_react/public'; + +interface SetupDependencies { + embeddable: ReturnType; + uiActions: IUiActionsSetup; +} + +interface StartDependencies { + embeddable: ReturnType; + inspector: InspectorStartContract; + uiActions: IUiActionsStart; +} + +export type Setup = void; +export type Start = void; + +export class DashboardEmbeddableContainerPublicPlugin + implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { embeddable, uiActions }: SetupDependencies): Setup { + const expandPanelAction = new ExpandPanelAction(); + uiActions.registerAction(expandPanelAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); + } + + public start(core: CoreStart, plugins: StartDependencies): Start { + const { application, notifications, overlays } = core; + const { embeddable, inspector, uiActions } = plugins; + + const SavedObjectFinder: React.FC< + Exclude + > = props => ( + + ); + + const useHideChrome = () => { + React.useEffect(() => { + core.chrome.setIsVisible(false); + return () => core.chrome.setIsVisible(true); + }, []); + }; + + const ExitFullScreenButton: React.FC = props => { + useHideChrome(); + return ; + }; + + const changeViewAction = new ReplacePanelAction( + core, + SavedObjectFinder, + notifications, + plugins.embeddable.getEmbeddableFactories + ); + uiActions.registerAction(changeViewAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); + + const factory = new DashboardContainerFactory({ + application, + notifications, + overlays, + embeddable, + inspector, + SavedObjectFinder, + ExitFullScreenButton, + uiActions, + }); + + embeddable.registerEmbeddableFactory(factory.type, factory); + } + + public stop() {} +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/test_helpers/get_sample_dashboard_input.ts b/src/plugins/dashboard_embeddable_container/public/test_helpers/get_sample_dashboard_input.ts similarity index 96% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/test_helpers/get_sample_dashboard_input.ts rename to src/plugins/dashboard_embeddable_container/public/test_helpers/get_sample_dashboard_input.ts index 98eb0636f9aad7..09478d8e8af354 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/test_helpers/get_sample_dashboard_input.ts +++ b/src/plugins/dashboard_embeddable_container/public/test_helpers/get_sample_dashboard_input.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ViewMode, EmbeddableInput } from '../embeddable_api'; +import { ViewMode, EmbeddableInput } from '../embeddable_plugin'; import { DashboardContainerInput, DashboardPanelState } from '../embeddable'; export function getSampleDashboardInput( diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/test_helpers/index.ts b/src/plugins/dashboard_embeddable_container/public/test_helpers/index.ts similarity index 100% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/test_helpers/index.ts rename to src/plugins/dashboard_embeddable_container/public/test_helpers/index.ts diff --git a/src/plugins/dashboard_embeddable_container/public/tests/dashboard_container.test.tsx b/src/plugins/dashboard_embeddable_container/public/tests/dashboard_container.test.tsx new file mode 100644 index 00000000000000..6a3b69af60d6b3 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/tests/dashboard_container.test.tsx @@ -0,0 +1,127 @@ +/* + * 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. + */ + +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; +import React from 'react'; +import { mount } from 'enzyme'; +import { nextTick } from 'test_utils/enzyme_helpers'; +import { I18nProvider } from '@kbn/i18n/react'; +import { ViewMode, CONTEXT_MENU_TRIGGER, EmbeddablePanel } from '../embeddable_plugin'; +import { DashboardContainer, DashboardContainerOptions } from '../embeddable/dashboard_container'; +import { getSampleDashboardInput } from '../test_helpers'; +import { + CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, + ContactCardEmbeddableInput, + ContactCardEmbeddable, + ContactCardEmbeddableOutput, + createEditModeAction, +} from '../embeddable_plugin_test_samples'; +// eslint-disable-next-line +import { embeddablePluginMock } from '../../../embeddable/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../../inspector/public/mocks'; +import { KibanaContextProvider } from '../../../kibana_react/public'; +// eslint-disable-next-line +import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; + +test('DashboardContainer in edit mode shows edit mode actions', async () => { + const inspector = inspectorPluginMock.createStartContract(); + const { setup, doStart } = embeddablePluginMock.createInstance(); + const uiActionsSetup = uiActionsPluginMock.createSetupContract(); + + const editModeAction = createEditModeAction(); + uiActionsSetup.registerAction(editModeAction); + uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction.id); + setup.registerEmbeddableFactory( + CONTACT_CARD_EMBEDDABLE, + new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) + ); + + const start = doStart(); + + const initialInput = getSampleDashboardInput({ viewMode: ViewMode.VIEW }); + const options: DashboardContainerOptions = { + application: {} as any, + embeddable: start, + notifications: {} as any, + overlays: {} as any, + inspector: {} as any, + SavedObjectFinder: () => null, + ExitFullScreenButton: () => null, + uiActions: {} as any, + }; + const container = new DashboardContainer(initialInput, options); + + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Bob', + }); + + const component = mount( + + + Promise.resolve([])} + getAllEmbeddableFactories={(() => []) as any} + getEmbeddableFactory={(() => null) as any} + notifications={{} as any} + overlays={{} as any} + inspector={inspector} + SavedObjectFinder={() => null} + /> + + + ); + + const button = findTestSubject(component, 'embeddablePanelToggleMenuIcon'); + + expect(button.length).toBe(1); + findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); + + expect(findTestSubject(component, `embeddablePanelContextMenuOpen`).length).toBe(1); + + const editAction = findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`); + + expect(editAction.length).toBe(0); + + container.updateInput({ viewMode: ViewMode.EDIT }); + await nextTick(); + component.update(); + findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); + await nextTick(); + component.update(); + expect(findTestSubject(component, 'embeddablePanelContextMenuOpen').length).toBe(0); + findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); + await nextTick(); + component.update(); + expect(findTestSubject(component, 'embeddablePanelContextMenuOpen').length).toBe(1); + + await nextTick(); + component.update(); + + // TODO: Address this. + // const action = findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`); + // expect(action.length).toBe(1); +}); diff --git a/src/plugins/dashboard_embeddable_container/public/types.ts b/src/plugins/dashboard_embeddable_container/public/types.ts new file mode 100644 index 00000000000000..9c2d6c0ab388de --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/types.ts @@ -0,0 +1,76 @@ +/* + * 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 { IconType } from '@elastic/eui'; +import { SavedObject as SavedObjectType, SavedObjectAttributes } from '../../../core/public'; + +export interface DashboardCapabilities { + showWriteControls: boolean; + createNew: boolean; +} + +// TODO: Replace Saved object interfaces by the ones Core will provide when it is ready. +export type SavedObjectAttribute = + | string + | number + | boolean + | null + | undefined + | SavedObjectAttributes + | SavedObjectAttributes[]; + +export interface SimpleSavedObject { + attributes: T; + _version?: SavedObjectType['version']; + id: SavedObjectType['id']; + type: SavedObjectType['type']; + migrationVersion: SavedObjectType['migrationVersion']; + error: SavedObjectType['error']; + references: SavedObjectType['references']; + get(key: string): any; + set(key: string, value: any): T; + has(key: string): boolean; + save(): Promise>; + delete(): void; +} + +export interface SavedObjectMetaData { + type: string; + name: string; + getIconForSavedObject(savedObject: SimpleSavedObject): IconType; + getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; + showSavedObject?(savedObject: SimpleSavedObject): boolean; +} + +interface FieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} + +export interface Field { + name: string; + type: string; + // esTypes might be undefined on old index patterns that have not been refreshed since we added + // this prop. It is also undefined on scripted fields. + esTypes?: string[]; + aggregatable: boolean; + filterable: boolean; + searchable: boolean; + subType?: FieldSubType; +} diff --git a/src/plugins/dashboard_embeddable_container/public/ui_actions_plugin.ts b/src/plugins/dashboard_embeddable_container/public/ui_actions_plugin.ts new file mode 100644 index 00000000000000..c8778025e77132 --- /dev/null +++ b/src/plugins/dashboard_embeddable_container/public/ui_actions_plugin.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 '../../../plugins/ui_actions/public'; diff --git a/src/plugins/data/common/es_query/__tests__/fields_mock.ts b/src/plugins/data/common/es_query/__tests__/fields_mock.ts new file mode 100644 index 00000000000000..83fdf588af00c8 --- /dev/null +++ b/src/plugins/data/common/es_query/__tests__/fields_mock.ts @@ -0,0 +1,320 @@ +/* + * 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 const fields = [ + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'ssl', + type: 'boolean', + esTypes: ['boolean'], + count: 20, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '@timestamp', + type: 'date', + esTypes: ['date'], + count: 30, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'time', + type: 'date', + esTypes: ['date'], + count: 30, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '@tags', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'utc_time', + type: 'date', + esTypes: ['date'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'phpmemory', + type: 'number', + esTypes: ['integer'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'ip', + type: 'ip', + esTypes: ['ip'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'request_body', + type: 'attachment', + esTypes: ['attachment'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'point', + type: 'geo_point', + esTypes: ['geo_point'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'area', + type: 'geo_shape', + esTypes: ['geo_shape'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'hashed', + type: 'murmur3', + esTypes: ['murmur3'], + count: 0, + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + name: 'geo.coordinates', + type: 'geo_point', + esTypes: ['geo_point'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'extension', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'machine.os', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'machine.os.raw', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { multi: { parent: 'machine.os' } }, + }, + { + name: 'geo.src', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '_id', + type: 'string', + esTypes: ['_id'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: '_type', + type: 'string', + esTypes: ['_type'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: '_source', + type: '_source', + esTypes: ['_source'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'non-filterable', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: false, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'non-sortable', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + name: 'custom_user_field', + type: 'conflict', + esTypes: ['long', 'text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'script string', + type: 'string', + count: 0, + scripted: true, + script: "'i am a string'", + lang: 'expression', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'script number', + type: 'number', + count: 0, + scripted: true, + script: '1234', + lang: 'expression', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'script date', + type: 'date', + count: 0, + scripted: true, + script: '1234', + lang: 'painless', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'script murmur3', + type: 'murmur3', + count: 0, + scripted: true, + script: '1234', + lang: 'expression', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'nestedField.child', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + subType: { nested: { path: 'nestedField' } }, + }, + { + name: 'nestedField.nestedChild.doublyNestedChild', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + subType: { nested: { path: 'nestedField.nestedChild' } }, + }, +]; + +export const getField = (name: string) => fields.find(field => field.name === name); diff --git a/packages/kbn-es-query/src/filters/lib/custom_filter.ts b/src/plugins/data/common/es_query/filters/custom_filter.ts similarity index 100% rename from packages/kbn-es-query/src/filters/lib/custom_filter.ts rename to src/plugins/data/common/es_query/filters/custom_filter.ts diff --git a/packages/kbn-es-query/src/filters/lib/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts similarity index 81% rename from packages/kbn-es-query/src/filters/lib/exists_filter.ts rename to src/plugins/data/common/es_query/filters/exists_filter.ts index 5843c25c43cffd..9125048e5f6cd0 100644 --- a/packages/kbn-es-query/src/filters/lib/exists_filter.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.ts @@ -18,6 +18,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; +import { IndexPattern, Field } from './types'; export type ExistsFilterMeta = FilterMeta; @@ -31,3 +32,14 @@ export type ExistsFilter = Filter & { }; export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists; + +export const buildExistsFilter = (field: Field, indexPattern: IndexPattern) => { + return { + meta: { + index: indexPattern.id, + }, + exists: { + field: field.name, + }, + } as ExistsFilter; +}; diff --git a/packages/kbn-es-query/src/filters/lib/geo_bounding_box_filter.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts similarity index 100% rename from packages/kbn-es-query/src/filters/lib/geo_bounding_box_filter.ts rename to src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts diff --git a/packages/kbn-es-query/src/filters/lib/geo_polygon_filter.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts similarity index 100% rename from packages/kbn-es-query/src/filters/lib/geo_polygon_filter.ts rename to src/plugins/data/common/es_query/filters/geo_polygon_filter.ts diff --git a/src/plugins/data/common/es_query/filters/index.ts b/src/plugins/data/common/es_query/filters/index.ts new file mode 100644 index 00000000000000..e28ce9ba74975c --- /dev/null +++ b/src/plugins/data/common/es_query/filters/index.ts @@ -0,0 +1,32 @@ +/* + * 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 './custom_filter'; +export * from './exists_filter'; +export * from './geo_bounding_box_filter'; +export * from './geo_polygon_filter'; +export * from './match_all_filter'; +export * from './meta_filter'; +export * from './missing_filter'; +export * from './phrase_filter'; +export * from './phrases_filter'; +export * from './query_string_filter'; +export * from './range_filter'; + +export * from './types'; diff --git a/packages/kbn-es-query/src/filters/lib/match_all_filter.ts b/src/plugins/data/common/es_query/filters/match_all_filter.ts similarity index 100% rename from packages/kbn-es-query/src/filters/lib/match_all_filter.ts rename to src/plugins/data/common/es_query/filters/match_all_filter.ts diff --git a/packages/kbn-es-query/src/filters/lib/meta_filter.ts b/src/plugins/data/common/es_query/filters/meta_filter.ts similarity index 75% rename from packages/kbn-es-query/src/filters/lib/meta_filter.ts rename to src/plugins/data/common/es_query/filters/meta_filter.ts index 8f6aef782cea24..9adfdc4eedcb33 100644 --- a/packages/kbn-es-query/src/filters/lib/meta_filter.ts +++ b/src/plugins/data/common/es_query/filters/meta_filter.ts @@ -55,7 +55,7 @@ export interface LatLon { lon: number; } -export function buildEmptyFilter(isPinned: boolean, index?: string): Filter { +export const buildEmptyFilter = (isPinned: boolean, index?: string): Filter => { const meta: FilterMeta = { disabled: false, negate: false, @@ -65,43 +65,43 @@ export function buildEmptyFilter(isPinned: boolean, index?: string): Filter { const $state: FilterState = { store: isPinned ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE, }; + return { meta, $state }; -} +}; -export function isFilterPinned(filter: Filter) { +export const isFilterPinned = (filter: Filter) => { return filter.$state && filter.$state.store === FilterStateStore.GLOBAL_STATE; -} +}; -export function toggleFilterDisabled(filter: Filter) { +export const toggleFilterDisabled = (filter: Filter) => { const disabled = !filter.meta.disabled; const meta = { ...filter.meta, disabled }; + return { ...filter, meta }; -} +}; -export function toggleFilterNegated(filter: Filter) { +export const toggleFilterNegated = (filter: Filter) => { const negate = !filter.meta.negate; const meta = { ...filter.meta, negate }; + return { ...filter, meta }; -} +}; -export function toggleFilterPinned(filter: Filter) { +export const toggleFilterPinned = (filter: Filter) => { const store = isFilterPinned(filter) ? FilterStateStore.APP_STATE : FilterStateStore.GLOBAL_STATE; const $state = { ...filter.$state, store }; + return { ...filter, $state }; -} +}; -export function enableFilter(filter: Filter) { - return !filter.meta.disabled ? filter : toggleFilterDisabled(filter); -} +export const enableFilter = (filter: Filter) => + !filter.meta.disabled ? filter : toggleFilterDisabled(filter); -export function disableFilter(filter: Filter) { - return filter.meta.disabled ? filter : toggleFilterDisabled(filter); -} +export const disableFilter = (filter: Filter) => + filter.meta.disabled ? filter : toggleFilterDisabled(filter); -export function pinFilter(filter: Filter) { - return isFilterPinned(filter) ? filter : toggleFilterPinned(filter); -} +export const pinFilter = (filter: Filter) => + isFilterPinned(filter) ? filter : toggleFilterPinned(filter); -export function unpinFilter(filter: Filter) { - return !isFilterPinned(filter) ? filter : toggleFilterPinned(filter); -} +export const unpinFilter = (filter: Filter) => + !isFilterPinned(filter) ? filter : toggleFilterPinned(filter); diff --git a/packages/kbn-es-query/src/filters/lib/missing_filter.ts b/src/plugins/data/common/es_query/filters/missing_filter.ts similarity index 100% rename from packages/kbn-es-query/src/filters/lib/missing_filter.ts rename to src/plugins/data/common/es_query/filters/missing_filter.ts diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts new file mode 100644 index 00000000000000..ec13e28c583d18 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter'; +import { IndexPattern } from './types'; +import { getField } from '../__tests__/fields_mock'; + +describe('Phrase filter builder', () => { + let indexPattern: IndexPattern; + + beforeEach(() => { + indexPattern = { + id: 'id', + }; + }); + + it('should be a function', () => { + expect(typeof buildPhraseFilter).toBe('function'); + }); + + it('should return a match query filter when passed a standard field', () => { + const field = getField('bytes'); + + expect(buildPhraseFilter(field, 5, indexPattern)).toEqual({ + meta: { + index: 'id', + }, + query: { + match_phrase: { + bytes: 5, + }, + }, + }); + }); + + it('should return a script filter when passed a scripted field', () => { + const field = getField('script number'); + + expect(buildPhraseFilter(field, 5, indexPattern)).toEqual({ + meta: { + index: 'id', + field: 'script number', + }, + script: { + script: { + lang: 'expression', + params: { + value: 5, + }, + source: '(1234) == value', + }, + }, + }); + }); +}); + +describe('buildInlineScriptForPhraseFilter', () => { + it('should wrap painless scripts in a lambda', () => { + const field = { + lang: 'painless', + script: 'return foo;', + }; + + const expected = + `boolean compare(Supplier s, def v) {return s.get() == v;}` + + `compare(() -> { return foo; }, params.value);`; + + expect(buildInlineScriptForPhraseFilter(field)).toBe(expected); + }); + + it('should create a simple comparison for other langs', () => { + const field = { + lang: 'expression', + script: 'doc[bytes].value', + }; + + const expected = `(doc[bytes].value) == value`; + + expect(buildInlineScriptForPhraseFilter(field)).toBe(expected); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts new file mode 100644 index 00000000000000..15c5c9d4ad2e6d --- /dev/null +++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts @@ -0,0 +1,144 @@ +/* + * 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 { get, isPlainObject } from 'lodash'; +import { Filter, FilterMeta } from './meta_filter'; +import { IndexPattern, Field } from './types'; + +export type PhraseFilterMeta = FilterMeta & { + params?: { + query: string; // The unformatted value + }; + script?: { + script: { + source?: any; + lang?: string; + params: any; + }; + }; + field?: any; + index?: any; +}; + +export type PhraseFilter = Filter & { + meta: PhraseFilterMeta; +}; + +type PhraseFilterValue = string | number | boolean; + +export const isPhraseFilter = (filter: any): filter is PhraseFilter => { + const isMatchPhraseQuery = filter && filter.query && filter.query.match_phrase; + + const isDeprecatedMatchPhraseQuery = + filter && + filter.query && + filter.query.match && + Object.values(filter.query.match).find((params: any) => params.type === 'phrase'); + + return !!(isMatchPhraseQuery || isDeprecatedMatchPhraseQuery); +}; + +export const isScriptedPhraseFilter = (filter: any): filter is PhraseFilter => + Boolean(get(filter, 'script.script.params.value')); + +export const getPhraseFilterField = (filter: PhraseFilter) => { + const queryConfig = filter.query.match_phrase || filter.query.match; + return Object.keys(queryConfig)[0]; +}; + +export const getPhraseFilterValue = (filter: PhraseFilter): PhraseFilterValue => { + const queryConfig = filter.query.match_phrase || filter.query.match; + const queryValue = Object.values(queryConfig)[0] as any; + return isPlainObject(queryValue) ? queryValue.query : queryValue; +}; + +export const buildPhraseFilter = ( + field: Field, + value: any, + indexPattern: IndexPattern +): PhraseFilter => { + const convertedValue = getConvertedValueForField(field, value); + + if (field.scripted) { + return { + meta: { index: indexPattern.id, field: field.name } as PhraseFilterMeta, + script: getPhraseScript(field, value), + } as PhraseFilter; + } else { + return { + meta: { index: indexPattern.id }, + query: { + match_phrase: { + [field.name]: convertedValue, + }, + }, + } as PhraseFilter; + } +}; + +export const getPhraseScript = (field: Field, value: string) => { + const convertedValue = getConvertedValueForField(field, value); + const script = buildInlineScriptForPhraseFilter(field); + + return { + script: { + source: script, + lang: field.lang, + params: { + value: convertedValue, + }, + }, + }; +}; + +// See https://github.com/elastic/elasticsearch/issues/20941 and https://github.com/elastic/kibana/issues/8677 +// and https://github.com/elastic/elasticsearch/pull/22201 +// for the reason behind this change. Aggs now return boolean buckets with a key of 1 or 0. +export const getConvertedValueForField = (field: Field, value: any) => { + if (typeof value !== 'boolean' && field.type === 'boolean') { + if ([1, 'true'].includes(value)) { + return true; + } else if ([0, 'false'].includes(value)) { + return false; + } else { + throw new Error(`${value} is not a valid boolean value for boolean field ${field.name}`); + } + } + return value; +}; + +/** + * Takes a scripted field and returns an inline script appropriate for use in a script query. + * Handles lucene expression and Painless scripts. Other langs aren't guaranteed to generate valid + * scripts. + * + * @param {object} scriptedField A Field object representing a scripted field + * @returns {string} The inline script string + */ +export const buildInlineScriptForPhraseFilter = (scriptedField: any) => { + // We must wrap painless scripts in a lambda in case they're more than a simple expression + if (scriptedField.lang === 'painless') { + return ( + `boolean compare(Supplier s, def v) {return s.get() == v;}` + + `compare(() -> { ${scriptedField.script} }, params.value);` + ); + } else { + return `(${scriptedField.script}) == value`; + } +}; diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts new file mode 100644 index 00000000000000..e4606695c0f6a5 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -0,0 +1,70 @@ +/* + * 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 { Filter, FilterMeta } from './meta_filter'; +import { Field, IndexPattern } from './types'; +import { getPhraseScript } from './phrase_filter'; + +export type PhrasesFilterMeta = FilterMeta & { + params: string[]; // The unformatted values + field?: string; +}; + +export type PhrasesFilter = Filter & { + meta: PhrasesFilterMeta; +}; + +export const isPhrasesFilter = (filter: any): filter is PhrasesFilter => + filter && filter.meta.type === 'phrases'; + +// Creates a filter where the given field matches one or more of the given values +// params should be an array of values +export const buildPhrasesFilter = (field: Field, params: any, indexPattern: IndexPattern) => { + const index = indexPattern.id; + const type = 'phrases'; + const key = field.name; + + const format = (f: Field, value: any) => + f && f.format && f.format.convert ? f.format.convert(value) : value; + + const value = params.map((v: any) => format(field, v)).join(', '); + + let should; + if (field.scripted) { + should = params.map((v: any) => ({ + script: getPhraseScript(field, v), + })); + } else { + should = params.map((v: any) => ({ + match_phrase: { + [field.name]: v, + }, + })); + } + + return { + meta: { index, type, key, value, params }, + query: { + bool: { + should, + minimum_should_match: 1, + }, + }, + } as PhrasesFilter; +}; diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts new file mode 100644 index 00000000000000..839e4f6359257e --- /dev/null +++ b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { buildQueryFilter } from './query_string_filter'; +import { IndexPattern } from './types'; + +describe('Phrase filter builder', () => { + let indexPattern: IndexPattern; + + beforeEach(() => { + indexPattern = { + id: 'id', + }; + }); + + it('should be a function', () => { + expect(typeof buildQueryFilter).toBe('function'); + }); + + it('should return a query filter when passed a standard field', () => { + expect(buildQueryFilter({ foo: 'bar' }, indexPattern.id, '')).toEqual({ + meta: { + alias: '', + index: 'id', + }, + query: { + foo: 'bar', + }, + }); + }); +}); diff --git a/packages/kbn-es-query/src/filters/lib/query_string_filter.ts b/src/plugins/data/common/es_query/filters/query_string_filter.ts similarity index 78% rename from packages/kbn-es-query/src/filters/lib/query_string_filter.ts rename to src/plugins/data/common/es_query/filters/query_string_filter.ts index 3b3b97fafba9bc..901dc724aa4e49 100644 --- a/packages/kbn-es-query/src/filters/lib/query_string_filter.ts +++ b/src/plugins/data/common/es_query/filters/query_string_filter.ts @@ -18,6 +18,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; +import { IndexPattern } from './types'; export type QueryStringFilterMeta = FilterMeta; @@ -32,3 +33,17 @@ export type QueryStringFilter = Filter & { export const isQueryStringFilter = (filter: any): filter is QueryStringFilter => filter && filter.query && filter.query.query_string; + +// Creates a filter corresponding to a raw Elasticsearch query DSL object +export const buildQueryFilter = ( + query: QueryStringFilter['query'], + index: IndexPattern, + alias: string +) => + ({ + query, + meta: { + index, + alias, + }, + } as QueryStringFilter); diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts new file mode 100644 index 00000000000000..9008dc2a672944 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -0,0 +1,174 @@ +/* + * 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 { each } from 'lodash'; +import { buildRangeFilter, RangeFilter } from './range_filter'; +import { IndexPattern, Field } from './types'; +import { getField } from '../__tests__/fields_mock'; + +describe('Range filter builder', () => { + let indexPattern: IndexPattern; + + beforeEach(() => { + indexPattern = { + id: 'id', + }; + }); + + it('should be a function', () => { + expect(typeof buildRangeFilter).toBe('function'); + }); + + it('should return a range filter when passed a standard field', () => { + const field = getField('bytes'); + + expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).toEqual({ + meta: { + index: 'id', + params: {}, + }, + range: { + bytes: { + gte: 1, + lte: 3, + }, + }, + }); + }); + + it('should return a script filter when passed a scripted field', () => { + const field = getField('script number'); + + expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).toEqual({ + meta: { + field: 'script number', + index: 'id', + params: {}, + }, + script: { + script: { + lang: 'expression', + source: '(' + field!.script + ')>=gte && (' + field!.script + ')<=lte', + params: { + value: '>=1 <=3', + gte: 1, + lte: 3, + }, + }, + }, + }); + }); + + it('should wrap painless scripts in comparator lambdas', () => { + const field = getField('script date'); + const expected = + `boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))} ` + + `boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}` + + `gte(() -> { ${field!.script} }, params.gte) && ` + + `lte(() -> { ${field!.script} }, params.lte)`; + + const rangeFilter = buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern); + + expect(rangeFilter.script!.script.source).toBe(expected); + }); + + it('should throw an error when gte and gt, or lte and lt are both passed', () => { + const field = getField('script number'); + + expect(() => { + buildRangeFilter(field, { gte: 1, gt: 3 }, indexPattern); + }).toThrowError(); + + expect(() => { + buildRangeFilter(field, { lte: 1, lt: 3 }, indexPattern); + }).toThrowError(); + }); + + it('to use the right operator for each of gte, gt, lt and lte', () => { + const field = getField('script number'); + + each({ gte: '>=', gt: '>', lte: '<=', lt: '<' }, (operator: string, key: any) => { + const params = { + [key]: 5, + }; + + const filter = buildRangeFilter(field, params, indexPattern); + const script = filter.script!.script; + + expect(script.source).toBe('(' + field!.script + ')' + operator + key); + expect(script.params[key]).toBe(5); + expect(script.params.value).toBe(operator + 5); + }); + }); + + describe('when given params where one side is infinite', () => { + let field: Field; + let filter: RangeFilter; + + beforeEach(() => { + field = getField('script number'); + filter = buildRangeFilter(field, { gte: 0, lt: Infinity }, indexPattern); + }); + + describe('returned filter', () => { + it('is a script filter', () => { + expect(filter).toHaveProperty('script'); + }); + + it('contain a param for the finite side', () => { + expect(filter.script!.script.params).toHaveProperty('gte', 0); + }); + + it('does not contain a param for the infinite side', () => { + expect(filter.script!.script.params).not.toHaveProperty('lt'); + }); + + it('does not contain a script condition for the infinite side', () => { + const script = field!.script; + + expect(filter.script!.script.source).toEqual(`(${script})>=gte`); + }); + }); + }); + + describe('when given params where both sides are infinite', () => { + let field: Field; + let filter: RangeFilter; + + beforeEach(() => { + field = getField('script number'); + filter = buildRangeFilter(field, { gte: -Infinity, lt: Infinity }, indexPattern); + }); + + describe('returned filter', () => { + it('is a match_all filter', () => { + expect(filter).not.toHaveProperty('script'); + expect(filter).toHaveProperty('match_all'); + }); + + it('does not contain params', () => { + expect(filter).not.toHaveProperty('params'); + }); + + it('meta field is set to field name', () => { + expect(filter.meta.field).toEqual('script number'); + }); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts new file mode 100644 index 00000000000000..d7931f191e52b1 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -0,0 +1,173 @@ +/* + * 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 { map, reduce, mapValues, get, keys, pick } from 'lodash'; +import { Filter, FilterMeta } from './meta_filter'; +import { Field, IndexPattern } from './types'; + +const OPERANDS_IN_RANGE = 2; + +const operators = { + gt: '>', + gte: '>=', + lte: '<=', + lt: '<', +}; +const comparators = { + gt: 'boolean gt(Supplier s, def v) {return s.get() > v}', + gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}', + lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}', + lt: 'boolean lt(Supplier s, def v) {return s.get() < v}', +}; + +const dateComparators = { + gt: 'boolean gt(Supplier s, def v) {return s.get().toInstant().isAfter(Instant.parse(v))}', + gte: 'boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))}', + lte: 'boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}', + lt: 'boolean lt(Supplier s, def v) {return s.get().toInstant().isBefore(Instant.parse(v))}', +}; + +export interface RangeFilterParams { + from?: number | string; + to?: number | string; + gt?: number | string; + lt?: number | string; + gte?: number | string; + lte?: number | string; + format?: string; +} + +const hasRangeKeys = (params: RangeFilterParams) => + Boolean( + keys(params).find((key: string) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key)) + ); + +export type RangeFilterMeta = FilterMeta & { + params: RangeFilterParams; + field?: any; + formattedValue?: string; +}; + +export type RangeFilter = Filter & { + meta: RangeFilterMeta; + script?: { + script: { + params: any; + lang: string; + source: any; + }; + }; + match_all?: any; + range: { [key: string]: RangeFilterParams }; +}; + +export const isRangeFilter = (filter: any): filter is RangeFilter => filter && filter.range; + +export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => { + const params: RangeFilterParams = get(filter, 'script.script.params', {}); + + return hasRangeKeys(params); +}; + +const formatValue = (field: Field, params: any[]) => + map(params, (val: any, key: string) => get(operators, key) + format(field, val)).join(' '); + +const format = (field: Field, value: any) => + field && field.format && field.format.convert ? field.format.convert(value) : value; + +// Creates a filter where the value for the given field is in the given range +// params should be an object containing `lt`, `lte`, `gt`, and/or `gte` +export const buildRangeFilter = ( + field: Field, + params: RangeFilterParams, + indexPattern: IndexPattern, + formattedValue?: string +): RangeFilter => { + const filter: any = { meta: { index: indexPattern.id, params: {} } }; + + if (formattedValue) { + filter.meta.formattedValue = formattedValue; + } + + params = mapValues(params, value => (field.type === 'number' ? parseFloat(value) : value)); + + if ('gte' in params && 'gt' in params) throw new Error('gte and gt are mutually exclusive'); + if ('lte' in params && 'lt' in params) throw new Error('lte and lt are mutually exclusive'); + + const totalInfinite = ['gt', 'lt'].reduce((acc: number, op: any) => { + const key = op in params ? op : `${op}e`; + const isInfinite = Math.abs(get(params, key)) === Infinity; + + if (isInfinite) { + acc++; + + // @ts-ignore + delete params[key]; + } + + return acc; + }, 0); + + if (totalInfinite === OPERANDS_IN_RANGE) { + filter.match_all = {}; + filter.meta.field = field.name; + } else if (field.scripted) { + filter.script = getRangeScript(field, params); + filter.script.script.params.value = formatValue(field, filter.script.script.params); + + filter.meta.field = field.name; + } else { + filter.range = {}; + filter.range[field.name] = params; + } + + return filter as RangeFilter; +}; + +export const getRangeScript = (field: IndexPattern, params: RangeFilterParams) => { + const knownParams = pick(params, (val, key: any) => key in operators); + let script = map( + knownParams, + (val: any, key: string) => '(' + field.script + ')' + get(operators, key) + key + ).join(' && '); + + // We must wrap painless scripts in a lambda in case they're more than a simple expression + if (field.lang === 'painless') { + const comp = field.type === 'date' ? dateComparators : comparators; + const currentComparators = reduce( + knownParams, + (acc, val, key) => acc.concat(get(comp, key)), + [] + ).join(' '); + + const comparisons = map( + knownParams, + (val, key) => `${key}(() -> { ${field.script} }, params.${key})` + ).join(' && '); + + script = `${currentComparators}${comparisons}`; + } + + return { + script: { + source: script, + params: knownParams, + lang: field.lang, + }, + }; +}; diff --git a/src/plugins/data/common/es_query/filters/types.ts b/src/plugins/data/common/es_query/filters/types.ts new file mode 100644 index 00000000000000..28147350619995 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/types.ts @@ -0,0 +1,57 @@ +/* + * 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 { ExistsFilter } from './exists_filter'; +import { GeoBoundingBoxFilter } from './geo_bounding_box_filter'; +import { GeoPolygonFilter } from './geo_polygon_filter'; +import { PhrasesFilter } from './phrases_filter'; +import { PhraseFilter } from './phrase_filter'; +import { RangeFilter } from './range_filter'; +import { MatchAllFilter } from './match_all_filter'; +import { MissingFilter } from './missing_filter'; + +// Any filter associated with a field (used in the filter bar/editor) +export type FieldFilter = + | ExistsFilter + | GeoBoundingBoxFilter + | GeoPolygonFilter + | PhraseFilter + | PhrasesFilter + | RangeFilter + | MatchAllFilter + | MissingFilter; + +export enum FILTERS { + CUSTOM = 'custom', + PHRASES = 'phrases', + PHRASE = 'phrase', + EXISTS = 'exists', + MATCH_ALL = 'match_all', + MISSING = 'missing', + QUERY_STRING = 'query_string', + RANGE = 'range', + GEO_BOUNDING_BOX = 'geo_bounding_box', + GEO_POLYGON = 'geo_polygon', +} + +// We can't import the real types from the data plugin, so need to either duplicate +// them here or figure out another solution, perhaps housing them in this package +// will be replaces after Fieds / IndexPattern will be moved into new platform +export type Field = any; +export type IndexPattern = any; diff --git a/src/plugins/data/common/es_query/index.ts b/src/plugins/data/common/es_query/index.ts new file mode 100644 index 00000000000000..88e14a43cfaae2 --- /dev/null +++ b/src/plugins/data/common/es_query/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. + */ +import * as esFilters from './filters'; + +export { esFilters }; diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts index 984cced336d2b5..6c00f11a408dc6 100644 --- a/src/plugins/data/common/field_formats/converters/url.ts +++ b/src/plugins/data/common/field_formats/converters/url.ts @@ -198,5 +198,3 @@ export class UrlFormat extends FieldFormat { } }; } - -// console.log(UrlFormat); diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index dca7897bd2766c..42b5a03fcc926a 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -20,5 +20,6 @@ export * from './query'; export * from './field_formats'; export * from './kbn_field_types'; +export * from './es_query'; export * from './types'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete_provider/types.ts index 4b1c5dfbd3528e..1f2d8f914dde3e 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete_provider/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { StaticIndexPattern } from 'ui/index_patterns'; +import { StaticIndexPattern, Field } from 'ui/index_patterns'; import { AutocompleteProviderRegister } from '.'; export type AutocompletePublicPluginSetup = Pick< @@ -50,11 +50,22 @@ export type AutocompleteSuggestionType = | 'conjunction' | 'recentSearch'; +// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm +// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the +// TypeScript compiler will narrow the type to the parts of the union that have a field prop. /** @public **/ -export interface AutocompleteSuggestion { +export type AutocompleteSuggestion = BasicAutocompleteSuggestion | FieldAutocompleteSuggestion; + +interface BasicAutocompleteSuggestion { description?: string; end: number; start: number; text: string; type: AutocompleteSuggestionType; + cursorIndex?: number; } + +export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & { + type: 'field'; + field: Field; +}; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 7e1b3801b62a45..32153df69f3678 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -34,3 +34,4 @@ export * from './types'; export { IRequestTypesMap, IResponseTypesMap } from './search'; export * from './search'; +export * from './query'; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 5e60ca93378d9a..4aae63c24d7fc7 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -18,6 +18,7 @@ */ import { Plugin } from '.'; import { searchSetupMock } from './search/mocks'; +import { queryServiceMock } from './query/mocks'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; @@ -29,19 +30,23 @@ const autocompleteMock: any = { }; const createSetupContract = (): Setup => { - const setupContract: Setup = { - autocomplete: autocompleteMock as Setup['autocomplete'], + const querySetupMock = queryServiceMock.createSetupContract(); + const setupContract = { + autocomplete: autocompleteMock, search: searchSetupMock, + query: querySetupMock, }; return setupContract; }; const createStartContract = (): Start => { - const startContract: Start = { - autocomplete: autocompleteMock as Start['autocomplete'], + const queryStartMock = queryServiceMock.createStartContract(); + const startContract = { + autocomplete: autocompleteMock, getSuggestions: jest.fn(), search: { search: jest.fn() }, + query: queryStartMock, }; return startContract; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 935a3c57545036..79db34c022b39f 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -18,23 +18,32 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; -import { AutocompleteProviderRegister } from './autocomplete_provider'; +import { Storage } from '../../kibana_utils/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from './types'; -import { SearchService } from './search/search_service'; +import { AutocompleteProviderRegister } from './autocomplete_provider'; import { getSuggestionsProvider } from './suggestions_provider'; +import { SearchService } from './search/search_service'; +import { QueryService } from './query'; export class DataPublicPlugin implements Plugin { private readonly autocomplete = new AutocompleteProviderRegister(); private readonly searchService: SearchService; + private readonly queryService: QueryService; constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(initializerContext); + this.queryService = new QueryService(); } public setup(core: CoreSetup): DataPublicPluginSetup { + const storage = new Storage(window.localStorage); return { autocomplete: this.autocomplete, search: this.searchService.setup(core), + query: this.queryService.setup({ + uiSettings: core.uiSettings, + storage, + }), }; } @@ -43,6 +52,7 @@ export class DataPublicPlugin implements Plugin { + return true; +}); + +describe('filter_manager', () => { + let updateSubscription: Subscription | undefined; + let fetchSubscription: Subscription | undefined; + let updateListener: sinon.SinonSpy; + + let filterManager: FilterManager; + let readyFilters: esFilters.Filter[]; + + beforeEach(() => { + updateListener = sinon.stub(); + filterManager = new FilterManager(setupMock.uiSettings); + readyFilters = getFiltersArray(); + }); + + afterEach(async () => { + if (updateSubscription) { + updateSubscription.unsubscribe(); + } + if (fetchSubscription) { + fetchSubscription.unsubscribe(); + } + + filterManager.removeAll(); + }); + + describe('observing', () => { + test('should return observable', () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + fetchSubscription = filterManager.getUpdates$().subscribe(() => {}); + expect(updateSubscription).toBeInstanceOf(Subscription); + expect(fetchSubscription).toBeInstanceOf(Subscription); + }); + }); + + describe('get \\ set filters', () => { + test('should be empty', () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + expect(filterManager.getFilters()).toHaveLength(0); + + const partitionedFiltres = filterManager.getPartitionedFilters(); + expect(partitionedFiltres.appFilters).toHaveLength(0); + expect(partitionedFiltres.globalFilters).toHaveLength(0); + expect(updateListener.called).toBeFalsy(); + }); + + test('app state should be set', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + filterManager.setFilters([f1]); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + expect(filterManager.getFilters()).toHaveLength(1); + + const partitionedFiltres = filterManager.getPartitionedFilters(); + expect(partitionedFiltres.appFilters).toHaveLength(1); + expect(partitionedFiltres.globalFilters).toHaveLength(0); + expect(updateListener.called).toBeTruthy(); + }); + + test('global state should be set', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + filterManager.setFilters([f1]); + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(filterManager.getFilters()).toHaveLength(1); + + const partitionedFiltres = filterManager.getPartitionedFilters(); + expect(partitionedFiltres.appFilters).toHaveLength(0); + expect(partitionedFiltres.globalFilters).toHaveLength(1); + expect(updateListener.called).toBeTruthy(); + }); + + test('both states should be set', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); + filterManager.setFilters([f1, f2]); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(filterManager.getFilters()).toHaveLength(2); + + const partitionedFiltres = filterManager.getPartitionedFilters(); + expect(partitionedFiltres.appFilters).toHaveLength(1); + expect(partitionedFiltres.globalFilters).toHaveLength(1); + + // listener should be called just once + expect(updateListener.called).toBeTruthy(); + expect(updateListener.callCount).toBe(1); + }); + + test('set state should override previous state', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); + + filterManager.setFilters([f1]); + filterManager.setFilters([f2]); + + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + expect(filterManager.getFilters()).toHaveLength(1); + + const partitionedFiltres = filterManager.getPartitionedFilters(); + expect(partitionedFiltres.appFilters).toHaveLength(1); + expect(partitionedFiltres.globalFilters).toHaveLength(0); + + // listener should be called just once + expect(updateListener.called).toBeTruthy(); + expect(updateListener.callCount).toBe(2); + }); + + test('changing a disabled filter should fire only update event', async function() { + const updateStub = jest.fn(); + const fetchStub = jest.fn(); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, true, false, 'age', 34); + + filterManager.setFilters([f1]); + + filterManager.getUpdates$().subscribe({ + next: updateStub, + }); + + filterManager.getFetches$().subscribe({ + next: fetchStub, + }); + + const f2 = _.cloneDeep(f1); + f2.meta.negate = true; + filterManager.setFilters([f2]); + + // this time, events should be emitted + expect(fetchStub).toBeCalledTimes(0); + expect(updateStub).toBeCalledTimes(1); + }); + }); + + describe('add filters', () => { + test('app state should accept a single filter', async function() { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + filterManager.addFilters(f1); + const appFilters = filterManager.getAppFilters(); + expect(appFilters).toHaveLength(1); + expect(appFilters[0]).toEqual(f1); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + expect(updateListener.callCount).toBe(1); + }); + + test('app state should accept array and preserve order', async () => { + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'female'); + + filterManager.addFilters([f1]); + filterManager.addFilters([f2]); + const appFilters = filterManager.getAppFilters(); + expect(appFilters).toHaveLength(2); + expect(appFilters).toEqual([f1, f2]); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + }); + + test('global state should accept a single filer', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + filterManager.addFilters(f1); + expect(filterManager.getAppFilters()).toHaveLength(0); + const globalFilters = filterManager.getGlobalFilters(); + expect(globalFilters).toHaveLength(1); + expect(globalFilters[0]).toEqual(f1); + expect(updateListener.callCount).toBe(1); + }); + + test('global state should be accept array and preserve order', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter( + esFilters.FilterStateStore.GLOBAL_STATE, + false, + false, + 'gender', + 'female' + ); + + filterManager.addFilters([f1, f2]); + expect(filterManager.getAppFilters()).toHaveLength(0); + const globalFilters = filterManager.getGlobalFilters(); + expect(globalFilters).toHaveLength(2); + expect(globalFilters).toEqual([f1, f2]); + }); + + test('mixed filters: global filters should stay in the beginning', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'female'); + filterManager.addFilters([f1, f2]); + const filters = filterManager.getFilters(); + expect(filters).toHaveLength(2); + expect(filters).toEqual([f1, f2]); + }); + + test('mixed filters: global filters should move to the beginning', async () => { + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + const f2 = getFilter( + esFilters.FilterStateStore.GLOBAL_STATE, + false, + false, + 'gender', + 'female' + ); + filterManager.addFilters([f1, f2]); + const filters = filterManager.getFilters(); + expect(filters).toHaveLength(2); + expect(filters).toEqual([f2, f1]); + }); + + test('add multiple filters at once', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter( + esFilters.FilterStateStore.GLOBAL_STATE, + false, + false, + 'gender', + 'female' + ); + filterManager.addFilters([f1, f2]); + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(2); + expect(updateListener.callCount).toBe(1); + }); + + test('add same filter to global and app', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + filterManager.addFilters([f1, f2]); + + // FILTER SHOULD BE ADDED ONLY ONCE, TO GLOBAL + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(updateListener.callCount).toBe(1); + }); + + test('add same filter with different values to global and app', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 38); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + filterManager.addFilters([f1, f2]); + + // FILTER SHOULD BE ADDED TWICE + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(updateListener.callCount).toBe(1); + }); + + test('add filter with no state, and force pin', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 38); + f1.$state = undefined; + + filterManager.addFilters([f1], true); + + // FILTER SHOULD BE GLOBAL + const f1Output = filterManager.getFilters()[0]; + expect(f1Output.$state).toBeDefined(); + if (f1Output.$state) { + expect(f1Output.$state.store).toBe(esFilters.FilterStateStore.GLOBAL_STATE); + } + }); + + test('add filter with no state, and dont force pin', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 38); + f1.$state = undefined; + + filterManager.addFilters([f1], false); + + // FILTER SHOULD BE APP + const f1Output = filterManager.getFilters()[0]; + expect(f1Output.$state).toBeDefined(); + if (f1Output.$state) { + expect(f1Output.$state.store).toBe(esFilters.FilterStateStore.APP_STATE); + } + }); + + test('should return app and global filters', async function() { + const filters = getFiltersArray(); + filterManager.addFilters(filters[0], false); + filterManager.addFilters(filters[1], true); + + // global filters should be listed first + let res = filterManager.getFilters(); + expect(res).toHaveLength(2); + expect(res[0].$state && res[0].$state.store).toEqual(esFilters.FilterStateStore.GLOBAL_STATE); + expect(res[0].meta.disabled).toEqual(filters[1].meta.disabled); + expect(res[0].query).toEqual(filters[1].query); + + expect(res[1].$state && res[1].$state.store).toEqual(esFilters.FilterStateStore.APP_STATE); + expect(res[1].meta.disabled).toEqual(filters[0].meta.disabled); + expect(res[1].query).toEqual(filters[0].query); + + // should return updated version of filters + filterManager.addFilters(filters[2], false); + + res = filterManager.getFilters(); + expect(res).toHaveLength(3); + }); + + test('should skip appStateStub filters that match globalStateStub filters', async function() { + filterManager.addFilters(readyFilters, true); + const appFilter = _.cloneDeep(readyFilters[1]); + filterManager.addFilters(appFilter, false); + + // global filters should be listed first + const res = filterManager.getFilters(); + expect(res).toHaveLength(3); + _.each(res, function(filter) { + expect(filter.$state && filter.$state.store).toBe(esFilters.FilterStateStore.GLOBAL_STATE); + }); + }); + + test('should allow overwriting a positive filter by a negated one', async function() { + // Add negate: false version of the filter + const filter = _.cloneDeep(readyFilters[0]); + filter.meta.negate = false; + + filterManager.addFilters(filter); + expect(filterManager.getFilters()).toHaveLength(1); + expect(filterManager.getFilters()[0]).toEqual(filter); + + // Add negate: true version of the same filter + const negatedFilter = _.cloneDeep(readyFilters[0]); + negatedFilter.meta.negate = true; + + filterManager.addFilters(negatedFilter); + // The negated filter should overwrite the positive one + expect(filterManager.getFilters()).toHaveLength(1); + expect(filterManager.getFilters()[0]).toEqual(negatedFilter); + }); + + test('should allow overwriting a negated filter by a positive one', async function() { + // Add negate: true version of the same filter + const negatedFilter = _.cloneDeep(readyFilters[0]); + negatedFilter.meta.negate = true; + + filterManager.addFilters(negatedFilter); + + // The negated filter should overwrite the positive one + expect(filterManager.getFilters()).toHaveLength(1); + expect(filterManager.getFilters()[0]).toEqual(negatedFilter); + + // Add negate: false version of the filter + const filter = _.cloneDeep(readyFilters[0]); + filter.meta.negate = false; + + filterManager.addFilters(filter); + expect(filterManager.getFilters()).toHaveLength(1); + expect(filterManager.getFilters()[0]).toEqual(filter); + }); + + test('should fire the update and fetch events', async function() { + const updateStub = jest.fn(); + const fetchStub = jest.fn(); + + filterManager.getUpdates$().subscribe({ + next: updateStub, + }); + + filterManager.getFetches$().subscribe({ + next: fetchStub, + }); + + filterManager.addFilters(readyFilters); + + // this time, events should be emitted + expect(fetchStub).toBeCalledTimes(1); + expect(updateStub).toBeCalledTimes(1); + }); + }); + + describe('filter reconciliation', function() { + test('should de-dupe app filters being added', async function() { + const newFilter = _.cloneDeep(readyFilters[1]); + filterManager.addFilters(readyFilters, false); + expect(filterManager.getFilters()).toHaveLength(3); + + filterManager.addFilters(newFilter, false); + expect(filterManager.getFilters()).toHaveLength(3); + }); + + test('should de-dupe global filters being added', async function() { + const newFilter = _.cloneDeep(readyFilters[1]); + filterManager.addFilters(readyFilters, true); + expect(filterManager.getFilters()).toHaveLength(3); + + filterManager.addFilters(newFilter, true); + expect(filterManager.getFilters()).toHaveLength(3); + }); + + test('should de-dupe global filters being set', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = _.cloneDeep(f1); + filterManager.setFilters([f1, f2]); + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(filterManager.getFilters()).toHaveLength(1); + }); + + test('should de-dupe app filters being set', async () => { + const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); + const f2 = _.cloneDeep(f1); + filterManager.setFilters([f1, f2]); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + expect(filterManager.getFilters()).toHaveLength(1); + }); + + test('should mutate global filters on appStateStub filter changes', async function() { + const idx = 1; + filterManager.addFilters(readyFilters, true); + + const appFilter = _.cloneDeep(readyFilters[idx]); + appFilter.meta.negate = true; + appFilter.$state = { + store: esFilters.FilterStateStore.APP_STATE, + }; + filterManager.addFilters(appFilter); + const res = filterManager.getFilters(); + expect(res).toHaveLength(3); + _.each(res, function(filter, i) { + expect(filter.$state && filter.$state.store).toBe('globalState'); + // make sure global filter actually mutated + expect(filter.meta.negate).toBe(i === idx); + }); + }); + + test('should merge conflicting app filters', async function() { + filterManager.addFilters(readyFilters, true); + const appFilter = _.cloneDeep(readyFilters[1]); + appFilter.meta.negate = true; + appFilter.$state = { + store: esFilters.FilterStateStore.APP_STATE, + }; + filterManager.addFilters(appFilter, false); + + // global filters should be listed first + const res = filterManager.getFilters(); + expect(res).toHaveLength(3); + expect( + res.filter(function(filter) { + return filter.$state && filter.$state.store === esFilters.FilterStateStore.GLOBAL_STATE; + }).length + ).toBe(3); + }); + + test('should enable disabled filters - global state', async function() { + // test adding to globalStateStub + const disabledFilters = _.map(readyFilters, function(filter) { + const f = _.cloneDeep(filter); + f.meta.disabled = true; + return f; + }); + filterManager.addFilters(disabledFilters, true); + filterManager.addFilters(readyFilters, true); + + const res = filterManager.getFilters(); + expect(res).toHaveLength(3); + expect( + res.filter(function(filter) { + return filter.meta.disabled === false; + }).length + ).toBe(3); + }); + + test('should enable disabled filters - app state', async function() { + // test adding to appStateStub + const disabledFilters = _.map(readyFilters, function(filter) { + const f = _.cloneDeep(filter); + f.meta.disabled = true; + return f; + }); + filterManager.addFilters(disabledFilters, true); + filterManager.addFilters(readyFilters, false); + + const res = filterManager.getFilters(); + expect(res).toHaveLength(3); + expect( + res.filter(function(filter) { + return filter.meta.disabled === false; + }).length + ).toBe(3); + }); + }); + + describe('remove filters', () => { + test('remove on empty should do nothing and not fire events', async () => { + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + filterManager.removeAll(); + expect(updateListener.called).toBeFalsy(); + expect(filterManager.getFilters()).toHaveLength(0); + }); + + test('remove on full should clean and fire events', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); + filterManager.setFilters([f1, f2]); + + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + filterManager.removeAll(); + expect(updateListener.called).toBeTruthy(); + expect(filterManager.getFilters()).toHaveLength(0); + }); + + test('remove non existing filter should do nothing and not fire events', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); + const f3 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'country', 'US'); + filterManager.setFilters([f1, f2]); + expect(filterManager.getFilters()).toHaveLength(2); + + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + await filterManager.removeFilter(f3); + expect(updateListener.called).toBeFalsy(); + expect(filterManager.getFilters()).toHaveLength(2); + }); + + test('remove existing filter should remove and fire events', async () => { + const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); + const f2 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE'); + const f3 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'country', 'US'); + filterManager.setFilters([f1, f2, f3]); + expect(filterManager.getFilters()).toHaveLength(3); + + updateSubscription = filterManager.getUpdates$().subscribe(updateListener); + await filterManager.removeFilter(f3); + expect(updateListener.called).toBeTruthy(); + expect(filterManager.getFilters()).toHaveLength(2); + }); + + test('should remove the filter from appStateStub', async function() { + filterManager.addFilters(readyFilters, false); + expect(filterManager.getAppFilters()).toHaveLength(3); + filterManager.removeFilter(readyFilters[0]); + expect(filterManager.getAppFilters()).toHaveLength(2); + }); + + test('should remove the filter from globalStateStub', async function() { + filterManager.addFilters(readyFilters, true); + expect(filterManager.getGlobalFilters()).toHaveLength(3); + filterManager.removeFilter(readyFilters[0]); + expect(filterManager.getGlobalFilters()).toHaveLength(2); + }); + + test('should fire the update and fetch events', async function() { + const updateStub = jest.fn(); + const fetchStub = jest.fn(); + + filterManager.addFilters(readyFilters, false); + + filterManager.getUpdates$().subscribe({ + next: updateStub, + }); + + filterManager.getFetches$().subscribe({ + next: fetchStub, + }); + + filterManager.removeFilter(readyFilters[0]); + + // this time, events should be emitted + expect(fetchStub).toBeCalledTimes(1); + expect(updateStub).toBeCalledTimes(1); + }); + + test('should remove matching filters', async function() { + filterManager.addFilters([readyFilters[0], readyFilters[1]], true); + filterManager.addFilters([readyFilters[2]], false); + + filterManager.removeFilter(readyFilters[0]); + + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + }); + + test('should remove matching filters by comparison', async function() { + filterManager.addFilters([readyFilters[0], readyFilters[1]], true); + filterManager.addFilters([readyFilters[2]], false); + + filterManager.removeFilter(_.cloneDeep(readyFilters[0])); + + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + + filterManager.removeFilter(_.cloneDeep(readyFilters[2])); + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + }); + + test('should do nothing with a non-matching filter', async function() { + filterManager.addFilters([readyFilters[0], readyFilters[1]], true); + filterManager.addFilters([readyFilters[2]], false); + + const missedFilter = _.cloneDeep(readyFilters[0]); + missedFilter.meta.negate = !readyFilters[0].meta.negate; + + filterManager.removeFilter(missedFilter); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(2); + }); + + test('should remove all the filters from both states', async function() { + filterManager.addFilters([readyFilters[0], readyFilters[1]], true); + filterManager.addFilters([readyFilters[2]], false); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(2); + + filterManager.removeAll(); + expect(filterManager.getAppFilters()).toHaveLength(0); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + }); + }); + + describe('invert', () => { + test('should fire the update and fetch events', async function() { + filterManager.addFilters(readyFilters); + expect(filterManager.getFilters()).toHaveLength(3); + + const updateStub = jest.fn(); + const fetchStub = jest.fn(); + filterManager.getUpdates$().subscribe({ + next: updateStub, + }); + + filterManager.getFetches$().subscribe({ + next: fetchStub, + }); + + readyFilters[1].meta.negate = !readyFilters[1].meta.negate; + filterManager.addFilters(readyFilters[1]); + expect(filterManager.getFilters()).toHaveLength(3); + expect(fetchStub).toBeCalledTimes(1); + expect(updateStub).toBeCalledTimes(1); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts similarity index 78% rename from src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts rename to src/plugins/data/public/query/filter_manager/filter_manager.ts index b3d6bd6873f502..06e2b77dca238f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -17,8 +17,6 @@ * under the License. */ -import { Filter, isFilterPinned, FilterStateStore } from '@kbn/es-query'; - import _ from 'lodash'; import { Subject } from 'rxjs'; @@ -28,10 +26,11 @@ import { compareFilters } from './lib/compare_filters'; import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; import { uniqFilters } from './lib/uniq_filters'; import { onlyDisabledFiltersChanged } from './lib/only_disabled'; -import { PartitionedFilters } from './partitioned_filters'; +import { PartitionedFilters } from './types'; +import { esFilters } from '../../../common/es_query'; export class FilterManager { - private filters: Filter[] = []; + private filters: esFilters.Filter[] = []; private updated$: Subject = new Subject(); private fetch$: Subject = new Subject(); private uiSettings: UiSettingsClientContract; @@ -40,7 +39,7 @@ export class FilterManager { this.uiSettings = uiSettings; } - private mergeIncomingFilters(partitionedFilters: PartitionedFilters): Filter[] { + private mergeIncomingFilters(partitionedFilters: PartitionedFilters): esFilters.Filter[] { const globalFilters = partitionedFilters.globalFilters; const appFilters = partitionedFilters.appFilters; @@ -61,25 +60,33 @@ export class FilterManager { return FilterManager.mergeFilters(appFilters, globalFilters); } - private static mergeFilters(appFilters: Filter[], globalFilters: Filter[]): Filter[] { + private static mergeFilters( + appFilters: esFilters.Filter[], + globalFilters: esFilters.Filter[] + ): esFilters.Filter[] { return uniqFilters(appFilters.reverse().concat(globalFilters.reverse())).reverse(); } - private static partitionFilters(filters: Filter[]): PartitionedFilters { - const [globalFilters, appFilters] = _.partition(filters, isFilterPinned); + private static partitionFilters(filters: esFilters.Filter[]): PartitionedFilters { + const [globalFilters, appFilters] = _.partition(filters, esFilters.isFilterPinned); return { globalFilters, appFilters, }; } - private handleStateUpdate(newFilters: Filter[]) { + private handleStateUpdate(newFilters: esFilters.Filter[]) { // global filters should always be first - newFilters.sort(({ $state: a }: Filter, { $state: b }: Filter): number => { - return a!.store === FilterStateStore.GLOBAL_STATE && - b!.store !== FilterStateStore.GLOBAL_STATE - ? -1 - : 1; + + newFilters.sort(({ $state: a }: esFilters.Filter, { $state: b }: esFilters.Filter): number => { + if (a!.store === b!.store) { + return 0; + } else { + return a!.store === esFilters.FilterStateStore.GLOBAL_STATE && + b!.store !== esFilters.FilterStateStore.GLOBAL_STATE + ? -1 + : 1; + } }); const filtersUpdated = !_.isEqual(this.filters, newFilters); @@ -124,7 +131,7 @@ export class FilterManager { /* Setters */ - public addFilters(filters: Filter[] | Filter, pinFilterStatus?: boolean) { + public addFilters(filters: esFilters.Filter[] | esFilters.Filter, pinFilterStatus?: boolean) { if (!Array.isArray(filters)) { filters = [filters]; } @@ -139,7 +146,10 @@ export class FilterManager { // Set the store of all filters. For now. // In the future, all filters should come in with filter state store already set. - const store = pinFilterStatus ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE; + const store = pinFilterStatus + ? esFilters.FilterStateStore.GLOBAL_STATE + : esFilters.FilterStateStore.APP_STATE; + FilterManager.setFiltersStore(filters, store); const mappedFilters = mapAndFlattenFilters(filters); @@ -154,14 +164,14 @@ export class FilterManager { this.handleStateUpdate(newFilters); } - public setFilters(newFilters: Filter[]) { + public setFilters(newFilters: esFilters.Filter[]) { const mappedFilters = mapAndFlattenFilters(newFilters); const newPartitionedFilters = FilterManager.partitionFilters(mappedFilters); const mergedFilters = this.mergeIncomingFilters(newPartitionedFilters); this.handleStateUpdate(mergedFilters); } - public removeFilter(filter: Filter) { + public removeFilter(filter: esFilters.Filter) { const filterIndex = _.findIndex(this.filters, item => { return _.isEqual(item.meta, filter.meta) && _.isEqual(item.query, filter.query); }); @@ -177,8 +187,8 @@ export class FilterManager { this.setFilters([]); } - public static setFiltersStore(filters: Filter[], store: FilterStateStore) { - _.map(filters, (filter: Filter) => { + public static setFiltersStore(filters: esFilters.Filter[], store: esFilters.FilterStateStore) { + _.map(filters, (filter: esFilters.Filter) => { // Override status only for filters that didn't have state in the first place. if (filter.$state === undefined) { filter.$state = { store }; diff --git a/src/plugins/data/public/query/filter_manager/index.ts b/src/plugins/data/public/query/filter_manager/index.ts new file mode 100644 index 00000000000000..7955cdd825ee6c --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { FilterManager } from './filter_manager'; + +export { uniqFilters } from './lib/uniq_filters'; +export { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; +export { onlyDisabledFiltersChanged } from './lib/only_disabled'; diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts new file mode 100644 index 00000000000000..6bde6b528d07bd --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { compareFilters } from './compare_filters'; +import { esFilters } from '../../../../common/es_query'; + +describe('filter manager utilities', () => { + describe('compare filters', () => { + test('should compare filters', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + const f2 = esFilters.buildEmptyFilter(true); + + expect(compareFilters(f1, f2)).toBeFalsy(); + }); + + test('should compare duplicates', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters(f1, f2)).toBeTruthy(); + }); + + test('should compare duplicates, ignoring meta attributes', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index1', + '' + ); + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index2', + '' + ); + + expect(compareFilters(f1, f2)).toBeTruthy(); + }); + + test('should compare duplicates, ignoring $state attributes', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + expect(compareFilters(f1, f2)).toBeTruthy(); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts similarity index 84% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts rename to src/plugins/data/public/query/filter_manager/lib/compare_filters.ts index 44bc333ae2b4fb..2a7cbe6e3303b5 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Filter, FilterMeta } from '@kbn/es-query'; import { defaults, isEqual, omit } from 'lodash'; +import { esFilters } from '../../../../common/es_query'; /** * Compare two filters to see if they match @@ -29,10 +29,14 @@ import { defaults, isEqual, omit } from 'lodash'; * * @returns {bool} Filters are the same */ -export const compareFilters = (first: Filter, second: Filter, comparatorOptions: any = {}) => { +export const compareFilters = ( + first: esFilters.Filter, + second: esFilters.Filter, + comparatorOptions: any = {} +) => { let comparators: any = {}; - const mapFilter = (filter: Filter) => { - const cleaned: FilterMeta = omit(filter, excludedAttributes); + const mapFilter = (filter: esFilters.Filter) => { + const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes); if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); diff --git a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts new file mode 100644 index 00000000000000..9b493add0886c6 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts @@ -0,0 +1,103 @@ +/* + * 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 { dedupFilters } from './dedup_filters'; +import { esFilters } from '../../../../common/es_query'; + +describe('filter manager utilities', () => { + describe('dedupFilters(existing, filters)', () => { + test('should return only filters which are not in the existing', () => { + const existing: esFilters.Filter[] = [ + esFilters.buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index', ''), + esFilters.buildQueryFilter( + { match: { _term: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + ]; + const filters: esFilters.Filter[] = [ + esFilters.buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index', ''), + esFilters.buildQueryFilter( + { match: { _term: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + ]; + const results = dedupFilters(existing, filters); + + expect(results).toContain(filters[0]); + expect(results).not.toContain(filters[1]); + }); + + test('should ignore the disabled attribute when comparing ', () => { + const existing: esFilters.Filter[] = [ + esFilters.buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index', ''), + { + ...esFilters.buildQueryFilter( + { match: { _term: { query: 'apache', type: 'phrase' } } }, + 'index1', + '' + ), + meta: { disabled: true, negate: false, alias: null }, + }, + ]; + const filters: esFilters.Filter[] = [ + esFilters.buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index', ''), + esFilters.buildQueryFilter( + { match: { _term: { query: 'apache', type: 'phrase' } } }, + 'index1', + '' + ), + ]; + const results = dedupFilters(existing, filters); + + expect(results).toContain(filters[0]); + expect(results).not.toContain(filters[1]); + }); + + test('should ignore $state attribute', () => { + const existing: esFilters.Filter[] = [ + esFilters.buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index', ''), + { + ...esFilters.buildQueryFilter( + { match: { _term: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + $state: { store: esFilters.FilterStateStore.APP_STATE }, + }, + ]; + const filters: esFilters.Filter[] = [ + esFilters.buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index', ''), + { + ...esFilters.buildQueryFilter( + { match: { _term: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + }, + ]; + const results = dedupFilters(existing, filters); + + expect(results).toContain(filters[0]); + expect(results).not.toContain(filters[1]); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts similarity index 86% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts rename to src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts index 9565cbd80b7791..6d6f49cb5e8338 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts @@ -17,9 +17,9 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { filter, find } from 'lodash'; import { compareFilters } from './compare_filters'; +import { esFilters } from '../../../../../../plugins/data/public'; /** * Combine 2 filter collections, removing duplicates @@ -31,8 +31,8 @@ import { compareFilters } from './compare_filters'; * @returns {object} An array of filters that were not in existing */ export const dedupFilters = ( - existingFilters: Filter[], - filters: Filter[], + existingFilters: esFilters.Filter[], + filters: esFilters.Filter[], comparatorOptions: any = {} ) => { if (!Array.isArray(filters)) { @@ -41,8 +41,8 @@ export const dedupFilters = ( return filter( filters, - (f: Filter) => - !find(existingFilters, (existingFilter: Filter) => + (f: esFilters.Filter) => + !find(existingFilters, (existingFilter: esFilters.Filter) => compareFilters(existingFilter, f, comparatorOptions) ) ); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts similarity index 89% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts rename to src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts index c0c509634aba2c..dfe3a093c66146 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts @@ -18,8 +18,8 @@ */ import sinon from 'sinon'; -import { Filter, buildEmptyFilter } from '@kbn/es-query'; import { generateMappingChain } from './generate_mapping_chain'; +import { esFilters } from '../../../../../../plugins/data/public'; describe('filter manager utilities', () => { let mapping: any; @@ -32,7 +32,7 @@ describe('filter manager utilities', () => { describe('generateMappingChain()', () => { test('should create a chaining function which calls the next function if the error is thrown', async () => { - const filter: Filter = buildEmptyFilter(true); + const filter = esFilters.buildEmptyFilter(true); mapping.throws(filter); next.returns('good'); @@ -45,7 +45,7 @@ describe('filter manager utilities', () => { }); test('should create a chaining function which DOES NOT call the next function if the result is returned', async () => { - const filter: Filter = buildEmptyFilter(true); + const filter = esFilters.buildEmptyFilter(true); mapping.returns('good'); next.returns('bad'); @@ -57,7 +57,7 @@ describe('filter manager utilities', () => { }); test('should resolve result for the mapping function', async () => { - const filter: Filter = buildEmptyFilter(true); + const filter = esFilters.buildEmptyFilter(true); mapping.returns({ key: 'test', value: 'example' }); @@ -70,7 +70,7 @@ describe('filter manager utilities', () => { test('should call the mapping function with the argument to the chain', async () => { // @ts-ignore - const filter: Filter = { test: 'example' }; + const filter: esFilters.Filter = { test: 'example' }; mapping.returns({ key: 'test', value: 'example' }); @@ -84,7 +84,7 @@ describe('filter manager utilities', () => { }); test('should resolve result for the next function', async () => { - const filter: Filter = buildEmptyFilter(true); + const filter = esFilters.buildEmptyFilter(true); mapping.throws(filter); next.returns({ key: 'test', value: 'example' }); @@ -98,7 +98,7 @@ describe('filter manager utilities', () => { }); test('should throw an error if no functions match', async done => { - const filter: Filter = buildEmptyFilter(true); + const filter = esFilters.buildEmptyFilter(true); mapping.throws(filter); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts similarity index 91% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts rename to src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts index 760270edd7170a..b6764389e0db9f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; const noop = () => { throw new Error('No mappings have been found for filter.'); }; export const generateMappingChain = (fn: Function, next: Function = noop) => { - return (filter: Filter) => { + return (filter: esFilters.Filter) => { try { return fn(filter); } catch (result) { diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts similarity index 93% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts rename to src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts index fce2aa0373ebe8..9a0d0d93698f69 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts @@ -17,14 +17,14 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { mapAndFlattenFilters } from './map_and_flatten_filters'; +import { esFilters } from '../../../../../data/public'; describe('filter manager utilities', () => { describe('mapAndFlattenFilters()', () => { let filters: unknown; - function getDisplayName(filter: Filter) { + function getDisplayName(filter: esFilters.Filter) { return typeof filter.meta.value === 'function' ? filter.meta.value() : filter.meta.value; } @@ -45,7 +45,7 @@ describe('filter manager utilities', () => { }); test('should map and flatten the filters', () => { - const results = mapAndFlattenFilters(filters as Filter[]); + const results = mapAndFlattenFilters(filters as esFilters.Filter[]); expect(results).toHaveLength(5); expect(results[0]).toHaveProperty('meta'); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts similarity index 80% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts rename to src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts index b350c3957b142d..5326d59f3e32b6 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts @@ -18,9 +18,9 @@ */ import { compact, flatten } from 'lodash'; -import { Filter } from '@kbn/es-query'; import { mapFilter } from './map_filter'; +import { esFilters } from '../../../../../data/public'; -export const mapAndFlattenFilters = (filters: Filter[]) => { - return compact(flatten(filters)).map((item: Filter) => mapFilter(item)); +export const mapAndFlattenFilters = (filters: esFilters.Filter[]) => { + return compact(flatten(filters)).map((item: esFilters.Filter) => mapFilter(item)); }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts similarity index 89% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts rename to src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts index c1d4ebfd3f7fc8..0d115125451eea 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts @@ -17,11 +17,11 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { mapFilter } from './map_filter'; +import { esFilters } from '../../../../../data/public'; describe('filter manager utilities', () => { - function getDisplayName(filter: Filter) { + function getDisplayName(filter: esFilters.Filter) { return typeof filter.meta.value === 'function' ? filter.meta.value() : filter.meta.value; } @@ -31,7 +31,7 @@ describe('filter manager utilities', () => { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } }, }; - const after = mapFilter(before as Filter); + const after = mapFilter(before as esFilters.Filter); expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', '_type'); @@ -43,7 +43,7 @@ describe('filter manager utilities', () => { test('should map exists filters', async () => { const before: any = { meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } }; - const after = mapFilter(before as Filter); + const after = mapFilter(before as esFilters.Filter); expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', '@timestamp'); @@ -55,7 +55,7 @@ describe('filter manager utilities', () => { test('should map missing filters', async () => { const before: any = { meta: { index: 'logstash-*' }, missing: { field: '@timestamp' } }; - const after = mapFilter(before as Filter); + const after = mapFilter(before as esFilters.Filter); expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', '@timestamp'); @@ -67,7 +67,7 @@ describe('filter manager utilities', () => { test('should map json filter', async () => { const before: any = { meta: { index: 'logstash-*' }, query: { match_all: {} } }; - const after = mapFilter(before as Filter); + const after = mapFilter(before as esFilters.Filter); expect(after).toHaveProperty('meta'); expect(after.meta).toHaveProperty('key', 'query'); @@ -81,7 +81,7 @@ describe('filter manager utilities', () => { const before: any = { meta: { index: 'logstash-*' } }; try { - mapFilter(before as Filter); + mapFilter(before as esFilters.Filter); } catch (e) { expect(e).toBeInstanceOf(Error); expect(e.message).toBe('No mappings have been found for filter.'); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts similarity index 81% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts rename to src/plugins/data/public/query/filter_manager/lib/map_filter.ts index c0d251e647fd10..2dc855caabfd36 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts @@ -17,22 +17,22 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { reduceRight } from 'lodash'; -import { mapMatchAll } from './map_match_all'; -import { mapPhrase } from './map_phrase'; -import { mapPhrases } from './map_phrases'; -import { mapRange } from './map_range'; -import { mapExists } from './map_exists'; -import { mapMissing } from './map_missing'; -import { mapQueryString } from './map_query_string'; -import { mapGeoBoundingBox } from './map_geo_bounding_box'; -import { mapGeoPolygon } from './map_geo_polygon'; -import { mapDefault } from './map_default'; +import { mapMatchAll } from './mappers/map_match_all'; +import { mapPhrase } from './mappers/map_phrase'; +import { mapPhrases } from './mappers/map_phrases'; +import { mapRange } from './mappers/map_range'; +import { mapExists } from './mappers/map_exists'; +import { mapMissing } from './mappers/map_missing'; +import { mapQueryString } from './mappers/map_query_string'; +import { mapGeoBoundingBox } from './mappers/map_geo_bounding_box'; +import { mapGeoPolygon } from './mappers/map_geo_polygon'; +import { mapDefault } from './mappers/map_default'; import { generateMappingChain } from './generate_mapping_chain'; +import { esFilters } from '../../../../../data/public'; -export function mapFilter(filter: Filter) { +export function mapFilter(filter: esFilters.Filter) { /** Mappers **/ // Each mapper is a simple promise function that test if the mapper can diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts similarity index 85% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts index acb6e89711033d..f10766901e5b7b 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { CustomFilter, buildEmptyFilter, buildQueryFilter } from '@kbn/es-query'; + import { mapDefault } from './map_default'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { describe('mapDefault()', () => { test('should return the key and value for matching filters', async () => { - const filter: CustomFilter = buildQueryFilter({ match_all: {} }, 'index'); + const filter = esFilters.buildQueryFilter({ match_all: {} }, 'index', ''); const result = mapDefault(filter); expect(result).toHaveProperty('key', 'query'); @@ -30,7 +31,7 @@ describe('filter manager utilities', () => { }); test('should return undefined if there is no valid key', async () => { - const filter = buildEmptyFilter(true) as CustomFilter; + const filter = esFilters.buildEmptyFilter(true); try { mapDefault(filter); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts similarity index 86% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts index 70c191879c22e9..fd84c5c742589b 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts @@ -17,15 +17,15 @@ * under the License. */ -import { Filter, FILTERS } from '@kbn/es-query'; import { find, keys, get } from 'lodash'; +import { esFilters } from '../../../../../common/es_query'; -export const mapDefault = (filter: Filter) => { +export const mapDefault = (filter: esFilters.Filter) => { const metaProperty = /(^\$|meta)/; const key = find(keys(filter), item => !item.match(metaProperty)); if (key) { - const type = FILTERS.CUSTOM; + const type = esFilters.FILTERS.CUSTOM; const value = JSON.stringify(get(filter, key, {})); return { type, key, value }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts similarity index 86% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts index c352d3e2b9a734..ff0ed4f4e4d94a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts @@ -16,14 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { ExistsFilter, buildEmptyFilter, buildExistsFilter } from '@kbn/es-query'; + import { mapExists } from './map_exists'; import { mapQueryString } from './map_query_string'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { describe('mapExists()', () => { test('should return the key and value for matching filters', async () => { - const filter: ExistsFilter = buildExistsFilter({ name: '_type' }, 'index'); + const filter = esFilters.buildExistsFilter({ name: '_type' }, 'index'); const result = mapExists(filter); expect(result).toHaveProperty('key', '_type'); @@ -31,7 +32,7 @@ describe('filter manager utilities', () => { }); test('should return undefined for none matching', async done => { - const filter = buildEmptyFilter(true) as ExistsFilter; + const filter = esFilters.buildEmptyFilter(true); try { mapQueryString(filter); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts similarity index 79% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts index d539219a1ca24e..63665bdd88ccbe 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts @@ -17,14 +17,14 @@ * under the License. */ -import { Filter, isExistsFilter, FILTERS } from '@kbn/es-query'; import { get } from 'lodash'; +import { esFilters } from '../../../../../common/es_query'; -export const mapExists = (filter: Filter) => { - if (isExistsFilter(filter)) { +export const mapExists = (filter: esFilters.Filter) => { + if (esFilters.isExistsFilter(filter)) { return { - type: FILTERS.EXISTS, - value: FILTERS.EXISTS, + type: esFilters.FILTERS.EXISTS, + value: esFilters.FILTERS.EXISTS, key: get(filter, 'exists.field'), }; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts similarity index 94% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts index c3c99e6f6c4a37..5fca4a652bad88 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts @@ -18,7 +18,7 @@ */ import { mapGeoBoundingBox } from './map_geo_bounding_box'; -import { Filter, GeoBoundingBoxFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { describe('mapGeoBoundingBox()', () => { @@ -34,7 +34,7 @@ describe('filter manager utilities', () => { bottom_right: { lat: 15, lon: 20 }, }, }, - } as GeoBoundingBoxFilter; + } as esFilters.GeoBoundingBoxFilter; const result = mapGeoBoundingBox(filter); @@ -63,7 +63,8 @@ describe('filter manager utilities', () => { bottom_right: { lat: 15, lon: 20 }, }, }, - } as GeoBoundingBoxFilter; + } as esFilters.GeoBoundingBoxFilter; + const result = mapGeoBoundingBox(filter); expect(result).toHaveProperty('key', 'point'); @@ -82,7 +83,7 @@ describe('filter manager utilities', () => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, - } as Filter; + } as esFilters.Filter; try { mapGeoBoundingBox(filter); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts similarity index 80% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts index 1f9b8cd842509b..091e9a3f34000a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts @@ -16,16 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { - GeoBoundingBoxFilter, - Filter, - FILTERS, - isGeoBoundingBoxFilter, - FilterValueFormatter, -} from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; const getFormattedValueFn = (params: any) => { - return (formatter?: FilterValueFormatter) => { + return (formatter?: esFilters.FilterValueFormatter) => { const corners = formatter ? { topLeft: formatter.convert(params.top_left), @@ -40,20 +34,20 @@ const getFormattedValueFn = (params: any) => { }; }; -const getParams = (filter: GeoBoundingBoxFilter) => { +const getParams = (filter: esFilters.GeoBoundingBoxFilter) => { const key = Object.keys(filter.geo_bounding_box).filter(k => k !== 'ignore_unmapped')[0]; const params = filter.geo_bounding_box[key]; return { key, params, - type: FILTERS.GEO_BOUNDING_BOX, + type: esFilters.FILTERS.GEO_BOUNDING_BOX, value: getFormattedValueFn(params), }; }; -export const mapGeoBoundingBox = (filter: Filter) => { - if (!isGeoBoundingBoxFilter(filter)) { +export const mapGeoBoundingBox = (filter: esFilters.Filter) => { + if (!esFilters.isGeoBoundingBoxFilter(filter)) { throw filter; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts similarity index 77% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts index ee4f9b295d682d..3afa3891a24bb8 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts @@ -16,23 +16,28 @@ * specific language governing permissions and limitations * under the License. */ + import { mapGeoPolygon } from './map_geo_polygon'; -import { GeoPolygonFilter, Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { - describe('mapGeoPolygon()', () => { - test('should return the key and value for matching filters with bounds', async () => { - const filter = { - meta: { - index: 'logstash-*', - }, - geo_polygon: { - point: { - points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }], - }, + let filter: esFilters.GeoPolygonFilter; + + beforeEach(() => { + filter = { + meta: { + index: 'logstash-*', + }, + geo_polygon: { + point: { + points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }], }, - } as GeoPolygonFilter; + }, + } as esFilters.GeoPolygonFilter; + }); + describe('mapGeoPolygon()', () => { + test('should return the key and value for matching filters with bounds', async () => { const result = mapGeoPolygon(filter); expect(result).toHaveProperty('key', 'point'); @@ -48,17 +53,6 @@ describe('filter manager utilities', () => { }); test('should return the key and value even when using ignore_unmapped', async () => { - const filter = { - meta: { - index: 'logstash-*', - }, - geo_polygon: { - ignore_unmapped: true, - point: { - points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }], - }, - }, - } as GeoPolygonFilter; const result = mapGeoPolygon(filter); expect(result).toHaveProperty('key', 'point'); @@ -74,15 +68,15 @@ describe('filter manager utilities', () => { }); test('should return undefined for none matching', async done => { - const filter = { + const wrongFilter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, - } as Filter; + } as esFilters.Filter; try { - mapGeoPolygon(filter); + mapGeoPolygon(wrongFilter); } catch (e) { - expect(e).toBe(filter); + expect(e).toBe(wrongFilter); done(); } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts similarity index 79% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts index 03ce4130d0c972..a7881b4a145a19 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts @@ -16,38 +16,33 @@ * specific language governing permissions and limitations * under the License. */ -import { - GeoPolygonFilter, - Filter, - FILTERS, - isGeoPolygonFilter, - FilterValueFormatter, -} from '@kbn/es-query'; + +import { esFilters } from '../../../../../common/es_query'; const POINTS_SEPARATOR = ', '; const getFormattedValueFn = (points: string[]) => { - return (formatter?: FilterValueFormatter) => { + return (formatter?: esFilters.FilterValueFormatter) => { return points .map((point: string) => (formatter ? formatter.convert(point) : JSON.stringify(point))) .join(POINTS_SEPARATOR); }; }; -function getParams(filter: GeoPolygonFilter) { +function getParams(filter: esFilters.GeoPolygonFilter) { const key = Object.keys(filter.geo_polygon).filter(k => k !== 'ignore_unmapped')[0]; const params = filter.geo_polygon[key]; return { key, params, - type: FILTERS.GEO_POLYGON, + type: esFilters.FILTERS.GEO_POLYGON, value: getFormattedValueFn(params.points || []), }; } -export function mapGeoPolygon(filter: Filter) { - if (!isGeoPolygonFilter(filter)) { +export function mapGeoPolygon(filter: esFilters.Filter) { + if (!esFilters.isGeoPolygonFilter(filter)) { throw filter; } return getParams(filter); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts similarity index 94% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts index 2f0641598a2ce3..4fc6d0b4924141 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { MatchAllFilter } from '@kbn/es-query'; + import { mapMatchAll } from './map_match_all'; +import { esFilters } from '../../../../../common/es_query'; describe('filter_manager/lib', () => { describe('mapMatchAll()', () => { - let filter: MatchAllFilter; + let filter: esFilters.MatchAllFilter; beforeEach(() => { filter = { diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts similarity index 81% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts index a1387e6dbe4574..4e93b1d41e9a82 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter, FILTERS, isMatchAllFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; -export const mapMatchAll = (filter: Filter) => { - if (isMatchAllFilter(filter)) { +export const mapMatchAll = (filter: esFilters.Filter) => { + if (esFilters.isMatchAllFilter(filter)) { return { - type: FILTERS.MATCH_ALL, + type: esFilters.FILTERS.MATCH_ALL, key: filter.meta.field, value: filter.meta.formattedValue || 'all', }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts similarity index 86% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts index ca23f25826906b..1847eb37ca42ff 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { MissingFilter, buildEmptyFilter, ExistsFilter } from '@kbn/es-query'; + import { mapMissing } from './map_missing'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { describe('mapMissing()', () => { test('should return the key and value for matching filters', async () => { - const filter: MissingFilter = { + const filter: esFilters.MissingFilter = { missing: { field: '_type' }, - ...buildEmptyFilter(true), + ...esFilters.buildEmptyFilter(true), }; const result = mapMissing(filter); @@ -33,7 +34,7 @@ describe('filter manager utilities', () => { }); test('should return undefined for none matching', async done => { - const filter = buildEmptyFilter(true) as ExistsFilter; + const filter = esFilters.buildEmptyFilter(true); try { mapMissing(filter); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts similarity index 78% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts index 861a84ed616468..51dee89ad884b2 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter, FILTERS, isMissingFilter } from '@kbn/es-query'; -export const mapMissing = (filter: Filter) => { - if (isMissingFilter(filter)) { +import { esFilters } from '../../../../../common/es_query'; + +export const mapMissing = (filter: esFilters.Filter) => { + if (esFilters.isMissingFilter(filter)) { return { - type: FILTERS.MISSING, - value: FILTERS.MISSING, + type: esFilters.FILTERS.MISSING, + value: esFilters.FILTERS.MISSING, key: filter.missing.field, }; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts similarity index 93% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts index c95a2529add149..05372d37264b06 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts @@ -17,7 +17,7 @@ * under the License. */ import { mapPhrase } from './map_phrase'; -import { PhraseFilter, Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { describe('mapPhrase()', () => { @@ -25,11 +25,13 @@ describe('filter manager utilities', () => { const filter = { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } }, - } as PhraseFilter; + } as esFilters.PhraseFilter; + const result = mapPhrase(filter); expect(result).toHaveProperty('value'); expect(result).toHaveProperty('key', '_type'); + if (result.value) { const displayName = result.value(); expect(displayName).toBe('apache'); @@ -40,7 +42,7 @@ describe('filter manager utilities', () => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, - } as Filter; + } as esFilters.Filter; try { mapPhrase(filter); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts new file mode 100644 index 00000000000000..b6e9c2007db970 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts @@ -0,0 +1,56 @@ +/* + * 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 { get } from 'lodash'; +import { esFilters } from '../../../../../common/es_query'; + +const getScriptedPhraseValue = (filter: esFilters.PhraseFilter) => + get(filter, ['script', 'script', 'params', 'value']); + +const getFormattedValueFn = (value: any) => { + return (formatter?: esFilters.FilterValueFormatter) => { + return formatter ? formatter.convert(value) : value; + }; +}; + +const getParams = (filter: esFilters.PhraseFilter) => { + const scriptedPhraseValue = getScriptedPhraseValue(filter); + const isScriptedFilter = Boolean(scriptedPhraseValue); + const key = isScriptedFilter ? filter.meta.field || '' : esFilters.getPhraseFilterField(filter); + const query = scriptedPhraseValue || esFilters.getPhraseFilterValue(filter); + const params = { query }; + + return { + key, + params, + type: esFilters.FILTERS.PHRASE, + value: getFormattedValueFn(query), + }; +}; + +export const isMapPhraseFilter = (filter: any): filter is esFilters.PhraseFilter => + esFilters.isPhraseFilter(filter) || esFilters.isScriptedPhraseFilter(filter); + +export const mapPhrase = (filter: esFilters.Filter) => { + if (!isMapPhraseFilter(filter)) { + throw filter; + } + + return getParams(filter); +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts similarity index 85% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts index c17ff11d49fd4a..7240d87d02b5ab 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts @@ -17,10 +17,10 @@ * under the License. */ -import { Filter, isPhrasesFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; -export const mapPhrases = (filter: Filter) => { - if (!isPhrasesFilter(filter)) { +export const mapPhrases = (filter: esFilters.Filter) => { + if (!esFilters.isPhrasesFilter(filter)) { throw filter; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts similarity index 81% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts index 4b1a5d39c405da..c60e7d3454fe0f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts @@ -17,27 +17,28 @@ * under the License. */ -import { QueryStringFilter, buildQueryFilter, buildEmptyFilter } from '@kbn/es-query'; import { mapQueryString } from './map_query_string'; +import { esFilters } from '../../../../../common/es_query'; describe('filter manager utilities', () => { describe('mapQueryString()', () => { test('should return the key and value for matching filters', async () => { - const filter: QueryStringFilter = buildQueryFilter( + const filter = esFilters.buildQueryFilter( { query_string: { query: 'foo:bar' } }, - 'index' + 'index', + '' ); - const result = mapQueryString(filter); + const result = mapQueryString(filter as esFilters.Filter); expect(result).toHaveProperty('key', 'query'); expect(result).toHaveProperty('value', 'foo:bar'); }); test('should return undefined for none matching', async done => { - const filter = buildEmptyFilter(true) as QueryStringFilter; + const filter = esFilters.buildEmptyFilter(true); try { - mapQueryString(filter); + mapQueryString(filter as esFilters.Filter); } catch (e) { expect(e).toBe(filter); done(); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts similarity index 81% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts index 94da8074edd04d..20c3555639a3ed 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter, FILTERS, isQueryStringFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../common/es_query'; -export const mapQueryString = (filter: Filter) => { - if (isQueryStringFilter(filter)) { +export const mapQueryString = (filter: esFilters.Filter) => { + if (esFilters.isQueryStringFilter(filter)) { return { - type: FILTERS.QUERY_STRING, + type: esFilters.FILTERS.QUERY_STRING, key: 'query', value: filter.query.query_string.query, }; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts new file mode 100644 index 00000000000000..c0d5773d6f2c14 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts @@ -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 { mapRange } from './map_range'; +import { esFilters } from '../../../../../common/es_query'; + +describe('filter manager utilities', () => { + describe('mapRange()', () => { + test('should return the key and value for matching filters with gt/lt', async () => { + const filter = { + meta: { index: 'logstash-*' } as esFilters.FilterMeta, + range: { bytes: { lt: 2048, gt: 1024 } }, + } as esFilters.RangeFilter; + const result = mapRange(filter); + + expect(result).toHaveProperty('key', 'bytes'); + expect(result).toHaveProperty('value'); + if (result.value) { + const displayName = result.value(); + expect(displayName).toBe('1024 to 2048'); + } + }); + + test('should return undefined for none matching', async done => { + const filter = { + meta: { index: 'logstash-*' }, + query: { query_string: { query: 'foo:bar' } }, + } as esFilters.Filter; + + try { + mapRange(filter); + } catch (e) { + expect(e).toBe(filter); + + done(); + } + }); + }); +}); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts new file mode 100644 index 00000000000000..51fb970f5f03ea --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts @@ -0,0 +1,65 @@ +/* + * 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 { get, has } from 'lodash'; +import { esFilters } from '../../../../../common/es_query'; + +const getFormattedValueFn = (left: any, right: any) => { + return (formatter?: esFilters.FilterValueFormatter) => { + let displayValue = `${left} to ${right}`; + if (formatter) { + const convert = formatter.getConverterFor('text'); + displayValue = `${convert(left)} to ${convert(right)}`; + } + return displayValue; + }; +}; + +const getFirstRangeKey = (filter: esFilters.RangeFilter) => + filter.range && Object.keys(filter.range)[0]; +const getRangeByKey = (filter: esFilters.RangeFilter, key: string) => get(filter, ['range', key]); + +function getParams(filter: esFilters.RangeFilter) { + const isScriptedRange = esFilters.isScriptedRangeFilter(filter); + const key: string = (isScriptedRange ? filter.meta.field : getFirstRangeKey(filter)) || ''; + const params: any = isScriptedRange + ? get(filter, 'script.script.params') + : getRangeByKey(filter, key); + + let left = has(params, 'gte') ? params.gte : params.gt; + if (left == null) left = -Infinity; + + let right = has(params, 'lte') ? params.lte : params.lt; + if (right == null) right = Infinity; + + const value = getFormattedValueFn(left, right); + + return { type: esFilters.FILTERS.RANGE, key, value, params }; +} + +export const isMapRangeFilter = (filter: any): filter is esFilters.RangeFilter => + esFilters.isRangeFilter(filter) || esFilters.isScriptedRangeFilter(filter); + +export const mapRange = (filter: esFilters.Filter) => { + if (!isMapRangeFilter(filter)) { + throw filter; + } + + return getParams(filter); +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts similarity index 77% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts rename to src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts index 3fedcf97a625ac..b9731797c9ee36 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { onlyDisabledFiltersChanged } from './only_disabled'; +import { esFilters } from '../../../../../data/public'; describe('filter manager utilities', () => { describe('onlyDisabledFiltersChanged()', () => { @@ -27,20 +27,20 @@ describe('filter manager utilities', () => { { meta: { disabled: true } }, { meta: { disabled: true } }, { meta: { disabled: true } }, - ] as Filter[]; - const newFilters = [{ meta: { disabled: true } }] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [{ meta: { disabled: true } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true); }); test('should return false if there are no old filters', () => { - const newFilters = [{ meta: { disabled: false } }] as Filter[]; + const newFilters = [{ meta: { disabled: false } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, undefined)).toBe(false); }); test('should return false if there are no new filters', () => { - const filters = [{ meta: { disabled: false } }] as Filter[]; + const filters = [{ meta: { disabled: false } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(undefined, filters)).toBe(false); }); @@ -50,8 +50,8 @@ describe('filter manager utilities', () => { { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: false } }, - ] as Filter[]; - const newFilters = [{ meta: { disabled: false } }] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [{ meta: { disabled: false } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); @@ -61,8 +61,8 @@ describe('filter manager utilities', () => { { meta: { disabled: true } }, { meta: { disabled: true } }, { meta: { disabled: true } }, - ] as Filter[]; - const newFilters = [{ meta: { disabled: false } }] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [{ meta: { disabled: false } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); @@ -72,8 +72,8 @@ describe('filter manager utilities', () => { { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: false } }, - ] as Filter[]; - const newFilters = [{ meta: { disabled: true } }] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [{ meta: { disabled: true } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); @@ -83,8 +83,8 @@ describe('filter manager utilities', () => { { meta: { disabled: true } }, { meta: { disabled: true } }, { meta: { disabled: true } }, - ] as Filter[]; - const newFilters = [] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true); }); @@ -94,8 +94,8 @@ describe('filter manager utilities', () => { { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: false } }, - ] as Filter[]; - const newFilters = [] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); @@ -104,11 +104,11 @@ describe('filter manager utilities', () => { const filters = [ { meta: { disabled: true, negate: false } }, { meta: { disabled: true, negate: false } }, - ] as Filter[]; + ] as esFilters.Filter[]; const newFilters = [ { meta: { disabled: true, negate: true } }, { meta: { disabled: true, negate: true } }, - ] as Filter[]; + ] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true); }); @@ -118,8 +118,8 @@ describe('filter manager utilities', () => { { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: true } }, - ] as Filter[]; - const newFilters = [{ meta: { disabled: false } }] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [{ meta: { disabled: false } }] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); @@ -129,15 +129,15 @@ describe('filter manager utilities', () => { { meta: { disabled: true } }, { meta: { disabled: false } }, { meta: { disabled: true } }, - ] as Filter[]; - const newFilters = [] as Filter[]; + ] as esFilters.Filter[]; + const newFilters = [] as esFilters.Filter[]; expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); test('should not throw with null filters', () => { - const filters = [null, { meta: { disabled: true } }] as Filter[]; - const newFilters = [] as Filter[]; + const filters = [null, { meta: { disabled: true } }] as esFilters.Filter[]; + const newFilters = [] as esFilters.Filter[]; expect(() => { onlyDisabledFiltersChanged(newFilters, filters); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts similarity index 82% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts rename to src/plugins/data/public/query/filter_manager/lib/only_disabled.ts index 9c0b5f43acb3ef..0fb6894a297a1f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts +++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts @@ -17,17 +17,20 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { filter, isEqual } from 'lodash'; +import { esFilters } from '../../../../../../plugins/data/public'; -const isEnabled = (f: Filter) => f && f.meta && !f.meta.disabled; +const isEnabled = (f: esFilters.Filter) => f && f.meta && !f.meta.disabled; /** * Checks to see if only disabled filters have been changed * * @returns {bool} Only disabled filters */ -export const onlyDisabledFiltersChanged = (newFilters?: Filter[], oldFilters?: Filter[]) => { +export const onlyDisabledFiltersChanged = ( + newFilters?: esFilters.Filter[], + oldFilters?: esFilters.Filter[] +) => { // If it's the same - compare only enabled filters const newEnabledFilters = filter(newFilters || [], isEnabled); const oldEnabledFilters = filter(oldFilters || [], isEnabled); diff --git a/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts new file mode 100644 index 00000000000000..08eeabc1497e3c --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts @@ -0,0 +1,85 @@ +/* + * 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 { uniqFilters } from './uniq_filters'; +import { esFilters } from '../../../../../data/public'; + +describe('filter manager utilities', () => { + describe('niqFilter', () => { + test('should filter out dups', () => { + const before: esFilters.Filter[] = [ + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + ]; + const results = uniqFilters(before); + + expect(results).toHaveLength(1); + }); + + test('should filter out duplicates, ignoring meta attributes', () => { + const before: esFilters.Filter[] = [ + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index1', + '' + ), + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index2', + '' + ), + ]; + const results = uniqFilters(before); + + expect(results).toHaveLength(1); + }); + + test('should filter out duplicates, ignoring $state attributes', () => { + const before: esFilters.Filter[] = [ + { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }, + { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }, + ]; + const results = uniqFilters(before); + + expect(results).toHaveLength(1); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts similarity index 84% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts rename to src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts index 12e793253371e7..e96c52e6db3dec 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter } from '@kbn/es-query'; import { each, union } from 'lodash'; import { dedupFilters } from './dedup_filters'; +import { esFilters } from '../../../../../data/public'; /** * Remove duplicate filters from an array of filters @@ -28,10 +28,10 @@ import { dedupFilters } from './dedup_filters'; * @returns {object} The original filters array with duplicates removed */ -export const uniqFilters = (filters: Filter[], comparatorOptions: any = {}) => { - let results: Filter[] = []; +export const uniqFilters = (filters: esFilters.Filter[], comparatorOptions: any = {}) => { + let results: esFilters.Filter[] = []; - each(filters, (filter: Filter) => { + each(filters, (filter: esFilters.Filter) => { results = union(results, dedupFilters(results, [filter]), comparatorOptions); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_filters_array.ts b/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts similarity index 91% rename from src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_filters_array.ts rename to src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts index 27f627b477c359..aa047647c57516 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_filters_array.ts +++ b/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts @@ -17,9 +17,9 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; -export function getFiltersArray(): Filter[] { +export function getFiltersArray(): esFilters.Filter[] { return [ { query: { match: { extension: { query: 'jpg', type: 'phrase' } } }, diff --git a/src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter.ts b/src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter.ts new file mode 100644 index 00000000000000..adc72c961b08bb --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter.ts @@ -0,0 +1,45 @@ +/* + * 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 { esFilters } from '../../../../../../plugins/data/public'; + +export function getFilter( + store: esFilters.FilterStateStore, + disabled: boolean, + negated: boolean, + queryKey: string, + queryValue: any +): esFilters.Filter { + return { + $state: { + store, + }, + meta: { + index: 'logstash-*', + disabled, + negate: negated, + alias: null, + }, + query: { + match: { + [queryKey]: queryValue, + }, + }, + }; +} diff --git a/src/plugins/data/public/query/filter_manager/types.ts b/src/plugins/data/public/query/filter_manager/types.ts new file mode 100644 index 00000000000000..0b3dbca2d6e0a6 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/types.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. + */ + +import { esFilters } from '../../../../../plugins/data/public'; + +export interface PartitionedFilters { + globalFilters: esFilters.Filter[]; + appFilters: esFilters.Filter[]; +} diff --git a/src/plugins/data/public/query/index.tsx b/src/plugins/data/public/query/index.tsx new file mode 100644 index 00000000000000..9d7c2ffc56f70b --- /dev/null +++ b/src/plugins/data/public/query/index.tsx @@ -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 * from './query_service'; +export * from './filter_manager'; + +export * from './timefilter'; + +export * from './persisted_log'; diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts new file mode 100644 index 00000000000000..f2832b6b67fa2a --- /dev/null +++ b/src/plugins/data/public/query/mocks.ts @@ -0,0 +1,59 @@ +/* + * 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 { QueryService, QuerySetup } from '.'; +import { timefilterServiceMock } from './timefilter/timefilter_service.mock'; + +type QueryServiceClientContract = PublicMethodsOf; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + filterManager: jest.fn() as any, + timefilter: timefilterServiceMock.createSetupContract(), + }; + + return setupContract; +}; + +const createStartContractMock = () => { + const startContract = { + filterManager: jest.fn() as any, + timefilter: timefilterServiceMock.createStartContract(), + }; + + return startContract; +}; + +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mocked.setup.mockReturnValue(createSetupContractMock()); + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; +}; + +export const queryServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, +}; diff --git a/src/legacy/core_plugins/data/public/query/persisted_log/index.ts b/src/plugins/data/public/query/persisted_log/index.ts similarity index 100% rename from src/legacy/core_plugins/data/public/query/persisted_log/index.ts rename to src/plugins/data/public/query/persisted_log/index.ts diff --git a/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.test.ts b/src/plugins/data/public/query/persisted_log/persisted_log.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.test.ts rename to src/plugins/data/public/query/persisted_log/persisted_log.test.ts diff --git a/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts b/src/plugins/data/public/query/persisted_log/persisted_log.ts similarity index 95% rename from src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts rename to src/plugins/data/public/query/persisted_log/persisted_log.ts index e0e6a0d0c44e4c..553b0bf5ef7e03 100644 --- a/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts +++ b/src/plugins/data/public/query/persisted_log/persisted_log.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import * as Rx from 'rxjs'; import { map } from 'rxjs/operators'; -import { Storage } from '../../types'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; const defaultIsDuplicate = (oldItem: any, newItem: any) => { return _.isEqual(oldItem, newItem); @@ -37,12 +37,12 @@ export class PersistedLog { public maxLength?: number; public filterDuplicates?: boolean; public isDuplicate: (oldItem: T, newItem: T) => boolean; - public storage: Storage; + public storage: IStorageWrapper; public items: T[]; private update$ = new Rx.BehaviorSubject(undefined); - constructor(name: string, options: PersistedLogOptions = {}, storage: Storage) { + constructor(name: string, options: PersistedLogOptions = {}, storage: IStorageWrapper) { this.name = name; this.maxLength = typeof options.maxLength === 'string' diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts new file mode 100644 index 00000000000000..206f8ac284ec37 --- /dev/null +++ b/src/plugins/data/public/query/query_service.ts @@ -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 { UiSettingsClientContract } from 'src/core/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { FilterManager } from './filter_manager'; +import { TimefilterService, TimefilterSetup } from './timefilter'; + +/** + * Query Service + * @internal + */ + +export interface QueryServiceDependencies { + storage: IStorageWrapper; + uiSettings: UiSettingsClientContract; +} + +export class QueryService { + filterManager!: FilterManager; + timefilter!: TimefilterSetup; + + public setup({ uiSettings, storage }: QueryServiceDependencies) { + this.filterManager = new FilterManager(uiSettings); + + const timefilterService = new TimefilterService(); + this.timefilter = timefilterService.setup({ + uiSettings, + storage, + }); + + return { + filterManager: this.filterManager, + timefilter: this.timefilter, + }; + } + + public start() { + return { + filterManager: this.filterManager, + timefilter: this.timefilter, + }; + } + + public stop() { + // nothing to do here yet + } +} + +/** @public */ +export type QuerySetup = ReturnType; +export type QueryStart = ReturnType; diff --git a/src/legacy/core_plugins/data/public/timefilter/get_time.test.ts b/src/plugins/data/public/query/timefilter/get_time.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/get_time.test.ts rename to src/plugins/data/public/query/timefilter/get_time.test.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/get_time.ts b/src/plugins/data/public/query/timefilter/get_time.ts similarity index 94% rename from src/legacy/core_plugins/data/public/timefilter/get_time.ts rename to src/plugins/data/public/query/timefilter/get_time.ts index 18a43d789714d7..55ee6527fbb1a3 100644 --- a/src/legacy/core_plugins/data/public/timefilter/get_time.ts +++ b/src/plugins/data/public/query/timefilter/get_time.ts @@ -19,7 +19,9 @@ import dateMath from '@elastic/datemath'; import { TimeRange } from 'src/plugins/data/public'; -import { IndexPattern, Field } from '../index_patterns'; + +// TODO: remove this +import { IndexPattern, Field } from '../../../../../legacy/core_plugins/data/public/index_patterns'; interface CalculateBoundsOptions { forceNow?: Date; diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts new file mode 100644 index 00000000000000..a6260e782c12ff --- /dev/null +++ b/src/plugins/data/public/query/timefilter/index.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. + */ + +export { TimefilterService, TimefilterSetup } from './timefilter_service'; + +export * from './types'; +export { Timefilter, TimefilterContract } from './timefilter'; +export { TimeHistory, TimeHistoryContract } from './time_history'; +export { getTime } from './get_time'; +export { changeTimeFilter } from './lib/change_time_filter'; +export { extractTimeFilter } from './lib/extract_time_filter'; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts b/src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts similarity index 87% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts rename to src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts index 2e397ff931bb63..df3e33060b01f2 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts +++ b/src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { RangeFilter } from '@kbn/es-query'; import { changeTimeFilter } from './change_time_filter'; import { TimeRange } from 'src/plugins/data/public'; -import { timefilterServiceMock } from '../../../timefilter/timefilter_service.mock'; +import { timefilterServiceMock } from '../timefilter_service.mock'; +import { esFilters } from '../../../../../../plugins/data/public'; const timefilterMock = timefilterServiceMock.createSetupContract(); const timefilter = timefilterMock.timefilter; @@ -42,7 +42,7 @@ describe('changeTimeFilter()', () => { test('should change the timefilter to match the range gt/lt', () => { const filter: any = { range: { '@timestamp': { gt, lt } } }; - changeTimeFilter(timefilter, filter as RangeFilter); + changeTimeFilter(timefilter, filter as esFilters.RangeFilter); const { to, from } = timefilter.getTime(); @@ -52,7 +52,7 @@ describe('changeTimeFilter()', () => { test('should change the timefilter to match the range gte/lte', () => { const filter: any = { range: { '@timestamp': { gte: gt, lte: lt } } }; - changeTimeFilter(timefilter, filter as RangeFilter); + changeTimeFilter(timefilter, filter as esFilters.RangeFilter); const { to, from } = timefilter.getTime(); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts similarity index 83% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts rename to src/plugins/data/public/query/timefilter/lib/change_time_filter.ts index 8cd1ce5ba6c846..7943aab3c151f6 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts @@ -19,10 +19,10 @@ import moment from 'moment'; import { keys } from 'lodash'; -import { RangeFilter } from '@kbn/es-query'; -import { TimefilterContract } from '../../../timefilter'; +import { TimefilterContract } from '../timefilter'; +import { esFilters } from '../../../../../../plugins/data/public'; -export function convertRangeFilterToTimeRange(filter: RangeFilter) { +export function convertRangeFilterToTimeRange(filter: esFilters.RangeFilter) { const key = keys(filter.range)[0]; const values = filter.range[key]; @@ -32,6 +32,6 @@ export function convertRangeFilterToTimeRange(filter: RangeFilter) { }; } -export function changeTimeFilter(timeFilter: TimefilterContract, filter: RangeFilter) { +export function changeTimeFilter(timeFilter: TimefilterContract, filter: esFilters.RangeFilter) { timeFilter.setTime(convertRangeFilterToTimeRange(filter)); } diff --git a/src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.test.ts b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.test.ts rename to src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.test.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.ts b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.ts rename to src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts diff --git a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts new file mode 100644 index 00000000000000..981c50844c4f3e --- /dev/null +++ b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { extractTimeFilter } from './extract_time_filter'; +import { esFilters } from '../../../../../../plugins/data/public'; + +describe('filter manager utilities', () => { + describe('extractTimeFilter()', () => { + test('should detect timeFilter', async () => { + const filters: esFilters.Filter[] = [ + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'logstash-*', + '' + ), + esFilters.buildRangeFilter( + { name: 'time' }, + { gt: 1388559600000, lt: 1388646000000 }, + 'logstash-*' + ), + ]; + const result = await extractTimeFilter('time', filters); + + expect(result.timeRangeFilter).toEqual(filters[1]); + expect(result.restOfFilters[0]).toEqual(filters[0]); + }); + + test("should not return timeFilter when name doesn't match", async () => { + const filters: esFilters.Filter[] = [ + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'logstash-*', + '' + ), + esFilters.buildRangeFilter({ name: '@timestamp' }, { from: 1, to: 2 }, 'logstash-*', ''), + ]; + const result = await extractTimeFilter('time', filters); + + expect(result.timeRangeFilter).toBeUndefined(); + expect(result.restOfFilters).toEqual(filters); + }); + + test('should not return a non range filter, even when names match', async () => { + const filters: esFilters.Filter[] = [ + esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'logstash-*', + '' + ), + esFilters.buildPhraseFilter({ name: 'time' }, 'banana', 'logstash-*'), + ]; + const result = await extractTimeFilter('time', filters); + + expect(result.timeRangeFilter).toBeUndefined(); + expect(result.restOfFilters).toEqual(filters); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts similarity index 82% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts rename to src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts index 22bda5b21295ee..4281610cb63e4e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts @@ -18,13 +18,13 @@ */ import { keys, partition } from 'lodash'; -import { Filter, isRangeFilter, RangeFilter } from '@kbn/es-query'; +import { esFilters } from '../../../../../../plugins/data/public'; -export function extractTimeFilter(timeFieldName: string, filters: Filter[]) { - const [timeRangeFilter, restOfFilters] = partition(filters, (obj: Filter) => { +export function extractTimeFilter(timeFieldName: string, filters: esFilters.Filter[]) { + const [timeRangeFilter, restOfFilters] = partition(filters, (obj: esFilters.Filter) => { let key; - if (isRangeFilter(obj)) { + if (esFilters.isRangeFilter(obj)) { key = keys(obj.range)[0]; } @@ -33,6 +33,6 @@ export function extractTimeFilter(timeFieldName: string, filters: Filter[]) { return { restOfFilters, - timeRangeFilter: timeRangeFilter[0] as RangeFilter | undefined, + timeRangeFilter: timeRangeFilter[0] as esFilters.RangeFilter | undefined, }; } diff --git a/src/legacy/core_plugins/data/public/timefilter/lib/parse_querystring.ts b/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/lib/parse_querystring.ts rename to src/plugins/data/public/query/timefilter/lib/parse_querystring.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/time_history.ts b/src/plugins/data/public/query/timefilter/time_history.ts similarity index 90% rename from src/legacy/core_plugins/data/public/timefilter/time_history.ts rename to src/plugins/data/public/query/timefilter/time_history.ts index 22778d1adea3c4..e14c9ac0bc7ca7 100644 --- a/src/legacy/core_plugins/data/public/timefilter/time_history.ts +++ b/src/plugins/data/public/query/timefilter/time_history.ts @@ -19,13 +19,13 @@ import moment from 'moment'; import { TimeRange } from 'src/plugins/data/public'; -import { PersistedLog } from '../query/persisted_log'; -import { Storage } from '../types'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { PersistedLog } from '../persisted_log'; export class TimeHistory { private history: PersistedLog; - constructor(store: Storage) { + constructor(storage: IStorageWrapper) { const historyOptions = { maxLength: 10, filterDuplicates: true, @@ -33,7 +33,7 @@ export class TimeHistory { return oldItem.from === newItem.from && oldItem.to === newItem.to; }, }; - this.history = new PersistedLog('kibana.timepicker.timeHistory', historyOptions, store); + this.history = new PersistedLog('kibana.timepicker.timeHistory', historyOptions, storage); } add(time: TimeRange) { diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter.test.ts b/src/plugins/data/public/query/timefilter/timefilter.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/timefilter.test.ts rename to src/plugins/data/public/query/timefilter/timefilter.test.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts similarity index 97% rename from src/legacy/core_plugins/data/public/timefilter/timefilter.ts rename to src/plugins/data/public/query/timefilter/timefilter.ts index 14e167b0fd56e5..137e5100aa20e9 100644 --- a/src/legacy/core_plugins/data/public/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -20,13 +20,15 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -import { IndexPattern, TimeHistoryContract } from '../index'; +import { RefreshInterval, TimeRange, TimeHistoryContract } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { parseQueryString } from './lib/parse_querystring'; import { calculateBounds, getTime } from './get_time'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; +// TODO: remove! + export class Timefilter { // Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled private enabledUpdated$ = new BehaviorSubject(false); diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter_service.mock.ts b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/timefilter_service.mock.ts rename to src/plugins/data/public/query/timefilter/timefilter_service.mock.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts b/src/plugins/data/public/query/timefilter/timefilter_service.ts similarity index 87% rename from src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts rename to src/plugins/data/public/query/timefilter/timefilter_service.ts index cda9b93ef08aa6..831ccebedc9cc3 100644 --- a/src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.ts @@ -18,8 +18,8 @@ */ import { UiSettingsClientContract } from 'src/core/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from './index'; -import { Storage } from '../types'; /** * Filter Service @@ -28,16 +28,16 @@ import { Storage } from '../types'; export interface TimeFilterServiceDependencies { uiSettings: UiSettingsClientContract; - store: Storage; + storage: IStorageWrapper; } export class TimefilterService { - public setup({ uiSettings, store }: TimeFilterServiceDependencies): TimefilterSetup { + public setup({ uiSettings, storage }: TimeFilterServiceDependencies): TimefilterSetup { const timefilterConfig = { timeDefaults: uiSettings.get('timepicker:timeDefaults'), refreshIntervalDefaults: uiSettings.get('timepicker:refreshIntervalDefaults'), }; - const history = new TimeHistory(store); + const history = new TimeHistory(storage); const timefilter = new Timefilter(timefilterConfig, history); return { diff --git a/src/legacy/core_plugins/data/public/timefilter/types.ts b/src/plugins/data/public/query/timefilter/types.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/types.ts rename to src/plugins/data/public/query/timefilter/types.ts diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 5f94734fef0837..9939815c1efd19 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -22,15 +22,18 @@ export * from './autocomplete_provider/types'; import { AutocompletePublicPluginSetup, AutocompletePublicPluginStart } from '.'; import { ISearchSetup, ISearchStart } from './search'; import { IGetSuggestions } from './suggestions_provider/types'; +import { QuerySetup, QueryStart } from './query'; export interface DataPublicPluginSetup { autocomplete: AutocompletePublicPluginSetup; search: ISearchSetup; + query: QuerySetup; } export interface DataPublicPluginStart { autocomplete: AutocompletePublicPluginStart; getSuggestions: IGetSuggestions; search: ISearchStart; + query: QueryStart; } export { IGetSuggestions } from './suggestions_provider/types'; diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index df933167cee255..f0b6117b928cd8 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -25,6 +25,11 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { DataServerPlugin as Plugin }; +export { + IndexPatternsFetcher, + FieldDescriptor, + shouldReadFieldFromDocValues, +} from './index_patterns'; export * from './search'; diff --git a/src/plugins/data/server/index_patterns/fetcher/index.ts b/src/plugins/data/server/index_patterns/fetcher/index.ts new file mode 100644 index 00000000000000..19306696885dbe --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/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 './index_patterns_fetcher'; +export { shouldReadFieldFromDocValues } from './lib'; diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts new file mode 100644 index 00000000000000..5f1493a49ab7de --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -0,0 +1,87 @@ +/* + * 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 { APICaller } from 'src/core/server'; + +import { getFieldCapabilities, resolveTimePattern, createNoMatchingIndicesError } from './lib'; + +export interface FieldDescriptor { + aggregatable: boolean; + name: string; + readFromDocValues: boolean; + searchable: boolean; + type: string; + esTypes: string[]; + subType?: FieldSubType; +} + +interface FieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} + +export class IndexPatternsFetcher { + private _callDataCluster: APICaller; + + constructor(callDataCluster: APICaller) { + this._callDataCluster = callDataCluster; + } + + /** + * Get a list of field objects for an index pattern that may contain wildcards + * + * @param {Object} [options] + * @property {String} options.pattern The index pattern + * @property {Number} options.metaFields The list of underscore prefixed fields that should + * be left in the field list (all others are removed). + * @return {Promise>} + */ + async getFieldsForWildcard(options: { + pattern: string | string[]; + metaFields?: string[]; + }): Promise { + const { pattern, metaFields } = options; + return await getFieldCapabilities(this._callDataCluster, pattern, metaFields); + } + + /** + * Get a list of field objects for a time pattern + * + * @param {Object} [options={}] + * @property {String} options.pattern The moment compatible time pattern + * @property {Number} options.lookBack The number of indices we will pull mappings for + * @property {Number} options.metaFields The list of underscore prefixed fields that should + * be left in the field list (all others are removed). + * @return {Promise>} + */ + async getFieldsForTimePattern(options: { + pattern: string; + metaFields: string[]; + lookBack: number; + interval: string; + }) { + const { pattern, lookBack, metaFields } = options; + const { matches } = await resolveTimePattern(this._callDataCluster, pattern); + const indices = matches.slice(0, lookBack); + if (indices.length === 0) { + throw createNoMatchingIndicesError(pattern); + } + return await getFieldCapabilities(this._callDataCluster, indices, metaFields); + } +} diff --git a/src/legacy/server/index_patterns/service/lib/errors.ts b/src/plugins/data/server/index_patterns/fetcher/lib/errors.ts similarity index 100% rename from src/legacy/server/index_patterns/service/lib/errors.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/errors.ts diff --git a/src/legacy/server/index_patterns/service/lib/es_api.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.test.js similarity index 100% rename from src/legacy/server/index_patterns/service/lib/es_api.test.js rename to src/plugins/data/server/index_patterns/fetcher/lib/es_api.test.js diff --git a/src/legacy/server/index_patterns/service/lib/es_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts similarity index 99% rename from src/legacy/server/index_patterns/service/lib/es_api.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts index 63c1824784dbdd..92b64fafddd665 100644 --- a/src/legacy/server/index_patterns/service/lib/es_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts @@ -18,7 +18,6 @@ */ import { APICaller } from 'src/core/server'; -// @ts-ignore import { convertEsError } from './errors'; import { FieldCapsResponse } from './field_capabilities'; diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/__fixtures__/es_field_caps_response.json b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json similarity index 90% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/__fixtures__/es_field_caps_response.json rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json index 97f8c187e1680a..0d70f49b5e6b54 100644 --- a/src/legacy/server/index_patterns/service/lib/field_capabilities/__fixtures__/es_field_caps_response.json +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json @@ -221,6 +221,27 @@ "searchable": true, "aggregatable": true } + }, + "nested_object_parent": { + "nested": { + "type": "nested", + "searchable": false, + "aggregatable": false + } + }, + "nested_object_parent.child": { + "text": { + "type": "text", + "searchable": true, + "aggregatable": false + } + }, + "nested_object_parent.child.keyword": { + "keyword": { + "type": "keyword", + "searchable": true, + "aggregatable": true + } } } } diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_capabilities.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js similarity index 100% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/field_capabilities.test.js rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts similarity index 97% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/field_capabilities.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index 05e8d591f13a9e..c275fb714088e9 100644 --- a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -23,7 +23,7 @@ import { APICaller } from 'src/core/server'; import { callFieldCapsApi } from '../es_api'; import { FieldCapsResponse, readFieldCapsResponse } from './field_caps_response'; import { mergeOverrides } from './overrides'; -import { FieldDescriptor } from '../../index_patterns_service'; +import { FieldDescriptor } from '../../index_patterns_fetcher'; export function concatIfUniq(arr: T[], value: T) { return arr.includes(value) ? arr : arr.concat(value); diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js similarity index 76% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/field_caps_response.test.js rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index dfdafeea3cd786..cf4af615b95779 100644 --- a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -24,7 +24,7 @@ import sinon from 'sinon'; import * as shouldReadFieldFromDocValuesNS from './should_read_field_from_doc_values'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; -import { getKbnFieldType } from '../../../../../../plugins/data/common'; +import { getKbnFieldType } from '../../../../../../data/common'; import { readFieldCapsResponse } from './field_caps_response'; import esResponse from './__fixtures__/es_field_caps_response.json'; @@ -37,10 +37,10 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { describe('conflicts', () => { it('returns a field for each in response, no filtering', () => { const fields = readFieldCapsResponse(esResponse); - expect(fields).toHaveLength(22); + expect(fields).toHaveLength(24); }); - it('includes only name, type, esTypes, searchable, aggregatable, readFromDocValues, and maybe conflictDescriptions, parent, ' + + it('includes only name, type, esTypes, searchable, aggregatable, readFromDocValues, and maybe conflictDescriptions, ' + 'and subType of each field', () => { const responseClone = cloneDeep(esResponse); // try to trick it into including an extra field @@ -48,7 +48,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { const fields = readFieldCapsResponse(responseClone); fields.forEach(field => { - const fieldWithoutOptionalKeys = omit(field, 'conflictDescriptions', 'parent', 'subType'); + const fieldWithoutOptionalKeys = omit(field, 'conflictDescriptions', 'subType'); expect(Object.keys(fieldWithoutOptionalKeys)).toEqual([ 'name', @@ -65,8 +65,8 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { sandbox.spy(shouldReadFieldFromDocValuesNS, 'shouldReadFieldFromDocValues'); const fields = readFieldCapsResponse(esResponse); const conflictCount = fields.filter(f => f.type === 'conflict').length; - // +1 is for the object field which gets filtered out of the final return value from readFieldCapsResponse - sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 1); + // +2 is for the object and nested fields which get filtered out of the final return value from readFieldCapsResponse + sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 2); }); it('converts es types to kibana types', () => { @@ -132,20 +132,41 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { expect(mixSearchableOther.searchable).toBe(true); }); - it('returns multi fields with parent and subType keys describing the relationship', () => { + it('returns multi fields with a subType key describing the relationship', () => { const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'multi_parent.child'); - expect(child).toHaveProperty('parent', 'multi_parent'); - expect(child).toHaveProperty('subType', 'multi'); + expect(child).toHaveProperty('subType', { multi: { parent: 'multi_parent' } }); }); - it('should not confuse object children for multi field children', () => { + it('returns nested sub-fields with a subType key describing the relationship', () => { + const fields = readFieldCapsResponse(esResponse); + const child = fields.find(f => f.name === 'nested_object_parent.child'); + expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } }); + }); + + it('handles fields that are both nested and multi', () => { + const fields = readFieldCapsResponse(esResponse); + const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); + expect(child).toHaveProperty( + 'subType', + { + nested: { path: 'nested_object_parent' }, + multi: { parent: 'nested_object_parent.child' } + }); + }); + + it('does not include the field actually mapped as nested itself', () => { + const fields = readFieldCapsResponse(esResponse); + const child = fields.find(f => f.name === 'nested_object_parent'); + expect(child).toBeUndefined(); + }); + + it('should not confuse object children for multi or nested field children', () => { // We detect multi fields by finding fields that have a dot in their name and then looking - // to see if their parents are *not* object or nested fields. In the future we may want to - // add parent and subType info for object and nested fields but for now we don't need it. + // to see if their parents are *not* object fields. In the future we may want to + // add subType info for object fields but for now we don't need it. const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'object_parent.child'); - expect(child).not.toHaveProperty('parent'); expect(child).not.toHaveProperty('subType'); }); }); diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts similarity index 79% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/field_caps_response.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index cb67d7c79cfacf..06eb30db0b24bb 100644 --- a/src/legacy/server/index_patterns/service/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -18,9 +18,9 @@ */ import { uniq } from 'lodash'; -import { FieldDescriptor } from '../..'; +import { castEsToKbnFieldTypeName } from '../../../../../common'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; -import { castEsToKbnFieldTypeName } from '../../../../../../plugins/data/common'; +import { FieldDescriptor } from '../../../fetcher'; interface FieldCapObject { type: string; @@ -151,18 +151,38 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie return field.name.includes('.'); }); - // Discern which sub fields are multi fields. If the parent field is not an object or nested field - // the child must be a multi field. + // Determine the type of each sub field. subFields.forEach(field => { - const parentFieldName = field.name + const parentFieldNames = field.name .split('.') .slice(0, -1) - .join('.'); - const parentFieldCaps = kibanaFormattedCaps.find(caps => caps.name === parentFieldName); + .map((_, index, parentFieldNameParts) => { + return parentFieldNameParts.slice(0, index + 1).join('.'); + }); + const parentFieldCaps = parentFieldNames.map(parentFieldName => { + return kibanaFormattedCaps.find(caps => caps.name === parentFieldName); + }); + const parentFieldCapsAscending = parentFieldCaps.reverse(); + + if (parentFieldCaps && parentFieldCaps.length > 0) { + let subType = {}; + // If the parent field is not an object or nested field the child must be a multi field. + const firstParent = parentFieldCapsAscending[0]; + if (firstParent && !['object', 'nested'].includes(firstParent.type)) { + subType = { ...subType, multi: { parent: firstParent.name } }; + } + + // We need to know if any parent field is nested + const nestedParentCaps = parentFieldCapsAscending.find( + parentCaps => parentCaps && parentCaps.type === 'nested' + ); + if (nestedParentCaps) { + subType = { ...subType, nested: { path: nestedParentCaps.name } }; + } - if (parentFieldCaps && !['object', 'nested'].includes(parentFieldCaps.type)) { - field.parent = parentFieldName; - field.subType = 'multi'; + if (Object.keys(subType).length > 0) { + field.subType = subType; + } } }); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts new file mode 100644 index 00000000000000..6a541aff564714 --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { getFieldCapabilities } from './field_capabilities'; +export { FieldCapsResponse } from './field_caps_response'; +export { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/overrides.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/overrides.ts similarity index 95% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/overrides.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/overrides.ts index 6310bf02e4430d..518bfeccac01ab 100644 --- a/src/legacy/server/index_patterns/service/lib/field_capabilities/overrides.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/overrides.ts @@ -18,7 +18,7 @@ */ import { merge } from 'lodash'; -import { FieldDescriptor } from '../../index_patterns_service'; +import { FieldDescriptor } from '../../index_patterns_fetcher'; const OVERRIDES: Record> = { _source: { type: '_source' }, diff --git a/src/legacy/server/index_patterns/service/lib/field_capabilities/should_read_field_from_doc_values.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts similarity index 100% rename from src/legacy/server/index_patterns/service/lib/field_capabilities/should_read_field_from_doc_values.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/index.ts b/src/plugins/data/server/index_patterns/fetcher/lib/index.ts new file mode 100644 index 00000000000000..20e74d2b1a579d --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/lib/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { getFieldCapabilities, shouldReadFieldFromDocValues } from './field_capabilities'; +export { resolveTimePattern } from './resolve_time_pattern'; +export { createNoMatchingIndicesError } from './errors'; diff --git a/src/legacy/server/index_patterns/service/lib/resolve_time_pattern.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.test.js similarity index 100% rename from src/legacy/server/index_patterns/service/lib/resolve_time_pattern.test.js rename to src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.test.js diff --git a/src/legacy/server/index_patterns/service/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts similarity index 100% rename from src/legacy/server/index_patterns/service/lib/resolve_time_pattern.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts diff --git a/src/legacy/server/index_patterns/service/lib/time_pattern_to_wildcard.test.ts b/src/plugins/data/server/index_patterns/fetcher/lib/time_pattern_to_wildcard.test.ts similarity index 100% rename from src/legacy/server/index_patterns/service/lib/time_pattern_to_wildcard.test.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/time_pattern_to_wildcard.test.ts diff --git a/src/legacy/server/index_patterns/service/lib/time_pattern_to_wildcard.ts b/src/plugins/data/server/index_patterns/fetcher/lib/time_pattern_to_wildcard.ts similarity index 100% rename from src/legacy/server/index_patterns/service/lib/time_pattern_to_wildcard.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/time_pattern_to_wildcard.ts diff --git a/src/plugins/data/server/index_patterns/index.ts b/src/plugins/data/server/index_patterns/index.ts new file mode 100644 index 00000000000000..6937fa22c4e5d1 --- /dev/null +++ b/src/plugins/data/server/index_patterns/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 { IndexPatternsFetcher, FieldDescriptor, shouldReadFieldFromDocValues } from './fetcher'; +export { IndexPatternsService } from './index_patterns_service'; diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts new file mode 100644 index 00000000000000..30d199c0e522e4 --- /dev/null +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -0,0 +1,30 @@ +/* + * 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 } from 'kibana/server'; +import { Plugin } from '../../../../core/server'; +import { registerRoutes } from './routes'; + +export class IndexPatternsService implements Plugin { + public setup({ http, elasticsearch }: CoreSetup) { + registerRoutes(http, elasticsearch); + } + + public start() {} +} diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts new file mode 100644 index 00000000000000..7975e923e219bd --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -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 { first } from 'rxjs/operators'; +import { schema } from '@kbn/config-schema'; +import { + CoreSetup, + KibanaRequest, + RequestHandlerContext, + APICaller, + CallAPIOptions, +} from '../../../../core/server'; +import { IndexPatternsFetcher } from './fetcher'; + +export function registerRoutes(http: CoreSetup['http'], elasticsearch: CoreSetup['elasticsearch']) { + const getIndexPatternsService = async (request: KibanaRequest): Promise => { + const client = await elasticsearch.dataClient$.pipe(first()).toPromise(); + const callCluster: APICaller = ( + endpoint: string, + params?: Record, + options?: CallAPIOptions + ) => client.asScoped(request).callAsCurrentUser(endpoint, params, options); + return new Promise(resolve => resolve(new IndexPatternsFetcher(callCluster))); + }; + + const parseMetaFields = (metaFields: string | string[]) => { + let parsedFields: string[] = []; + if (typeof metaFields === 'string') { + parsedFields = JSON.parse(metaFields); + } else { + parsedFields = metaFields; + } + return parsedFields; + }; + + const router = http.createRouter(); + router.get( + { + path: '/api/index_patterns/_fields_for_wildcard', + validate: { + query: schema.object({ + pattern: schema.string(), + meta_fields: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { + defaultValue: [], + }), + }), + }, + }, + async (context: RequestHandlerContext, request: any, response: any) => { + const indexPatterns = await getIndexPatternsService(request); + const { pattern, meta_fields: metaFields } = request.query; + + let parsedFields: string[] = []; + try { + parsedFields = parseMetaFields(metaFields); + } catch (error) { + return response.badRequest(); + } + + try { + const fields = await indexPatterns.getFieldsForWildcard({ + pattern, + metaFields: parsedFields, + }); + + return response.ok({ + body: { fields }, + headers: { + 'content-type': 'application/json', + }, + }); + } catch (error) { + return response.notFound(); + } + } + ); + + router.get( + { + path: '/api/index_patterns/_fields_for_time_pattern', + validate: { + query: schema.object({ + pattern: schema.string(), + interval: schema.maybe(schema.string()), + look_back: schema.number({ min: 1 }), + meta_fields: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { + defaultValue: [], + }), + }), + }, + }, + async (context: RequestHandlerContext, request: any, response: any) => { + const indexPatterns = await getIndexPatternsService(request); + const { pattern, interval, look_back: lookBack, meta_fields: metaFields } = request.query; + + let parsedFields: string[] = []; + try { + parsedFields = parseMetaFields(metaFields); + } catch (error) { + return response.badRequest(); + } + + try { + const fields = await indexPatterns.getFieldsForTimePattern({ + pattern, + interval: interval ? interval : '', + lookBack, + metaFields: parsedFields, + }); + + return response.ok({ + body: { fields }, + headers: { + 'content-type': 'application/json', + }, + }); + } catch (error) { + return response.notFound(); + } + } + ); +} diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 9cf08b0702e9e0..e81250e653ebd3 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -18,19 +18,21 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/server'; +import { IndexPatternsService } from './index_patterns'; import { ISearchSetup } from './search'; import { SearchService } from './search/search_service'; export interface DataPluginSetup { search: ISearchSetup; } - export class DataServerPlugin implements Plugin { private readonly searchService: SearchService; + private readonly indexPatterns = new IndexPatternsService(); constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(initializerContext); } public setup(core: CoreSetup) { + this.indexPatterns.setup(core); return { search: this.searchService.setup(core), }; diff --git a/src/plugins/embeddable/public/_index.scss b/src/plugins/embeddable/public/_index.scss new file mode 100644 index 00000000000000..ed80b3f9983e52 --- /dev/null +++ b/src/plugins/embeddable/public/_index.scss @@ -0,0 +1,3 @@ +@import './variables'; +@import './lib/panel/index'; +@import './lib/panel/panel_header/index'; diff --git a/src/legacy/core_plugins/embeddable_api/public/_variables.scss b/src/plugins/embeddable/public/_variables.scss similarity index 100% rename from src/legacy/core_plugins/embeddable_api/public/_variables.scss rename to src/plugins/embeddable/public/_variables.scss diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts index 99cfb2ea13d07b..e2592b70397f33 100644 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts +++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts @@ -18,16 +18,16 @@ */ import { i18n } from '@kbn/i18n'; -import { Filter } from '@kbn/es-query'; import { IAction, createAction, IncompatibleActionError } from '../ui_actions'; import { IEmbeddable, EmbeddableInput } from '../embeddables'; +import { esFilters } from '../../../../../plugins/data/public'; export const APPLY_FILTER_ACTION = 'APPLY_FILTER_ACTION'; -type RootEmbeddable = IEmbeddable; +type RootEmbeddable = IEmbeddable; interface ActionContext { embeddable: IEmbeddable; - filters: Filter[]; + filters: esFilters.Filter[]; } async function isCompatible(context: ActionContext) { 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 802be5bf1282ec..47113ffc59561c 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,7 +20,6 @@ import { ViewMode, EmbeddableOutput, isErrorEmbeddable } from '../../../../'; import { AddPanelAction } from './add_panel_action'; import { EmbeddableFactory } from '../../../../embeddables'; -import { Filter, FilterStateStore } from '@kbn/es-query'; import { FILTERABLE_EMBEDDABLE, FilterableEmbeddable, @@ -32,6 +31,7 @@ import { GetEmbeddableFactory } from '../../../../types'; // eslint-disable-next-line import { coreMock } from '../../../../../../../../core/public/mocks'; import { ContactCardEmbeddable } from '../../../../test_samples'; +import { esFilters } from '../../../../../../../../plugins/data/public'; const embeddableFactories = new Map(); embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory()); @@ -51,8 +51,8 @@ beforeEach(async () => { () => null ); - const derivedFilter: Filter = { - $state: { store: FilterStateStore.APP_STATE }, + const derivedFilter: esFilters.Filter = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx index dd55cd1eacdc21..fd8f286a9d8f6d 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx @@ -27,7 +27,8 @@ import { import { HelloWorldContainer } from '../../../../test_samples/embeddables/hello_world_container'; import { ContactCardEmbeddable } from '../../../../test_samples/embeddables/contact_card/contact_card_embeddable'; import { ContainerInput } from '../../../../containers'; -import { mount } from 'enzyme'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { ReactWrapper } from 'enzyme'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; @@ -55,7 +56,7 @@ test('createNewEmbeddable() add embeddable to container', async () => { }; const container = new HelloWorldContainer(input, { getEmbeddableFactory } as any); const onClose = jest.fn(); - const component = mount( + const component = mount( { notifications={core.notifications} SavedObjectFinder={() => null} /> - ); + ) as ReactWrapper; expect(Object.values(container.getInput().panels).length).toBe(0); component.instance().createNewEmbeddable(CONTACT_CARD_EMBEDDABLE); @@ -109,7 +110,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()' notifications={core.notifications} SavedObjectFinder={() => null} /> - ); + ) as ReactWrapper; const spy = jest.fn(); component.instance().createNewEmbeddable = spy; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 8efc9e5b996c53..4f2ae7ab19bcb3 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -22,21 +22,20 @@ import React from 'react'; import { CoreSetup } from 'src/core/public'; import { - EuiFlexGroup, - EuiFlexItem, + EuiButton, + EuiContextMenuItem, + EuiContextMenuPanel, EuiFlyout, EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, - // @ts-ignore - EuiSuperSelect, + EuiPopover, EuiTitle, - EuiText, } from '@elastic/eui'; import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; -import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types'; +import { GetEmbeddableFactories, GetEmbeddableFactory } from '../../../../types'; interface Props { onClose: () => void; @@ -47,9 +46,21 @@ interface Props { SavedObjectFinder: React.ComponentType; } -export class AddPanelFlyout extends React.Component { +interface State { + isCreateMenuOpen: boolean; +} + +function capitalize([first, ...letters]: string) { + return `${first.toUpperCase()}${letters.join('')}`; +} + +export class AddPanelFlyout extends React.Component { private lastToast: any; + public state = { + isCreateMenuOpen: false, + }; + constructor(props: Props) { super(props); } @@ -96,41 +107,27 @@ export class AddPanelFlyout extends React.Component { this.showToast(name); }; - private getSelectCreateNewOptions() { - const list = [ - { - value: 'createNew', - inputDisplay: ( - - - - ), - }, - ...[...this.props.getAllFactories()] - .filter( - factory => factory.isEditable() && !factory.isContainerType && factory.canCreateNew() - ) - .map(factory => ({ - inputDisplay: ( - - - - ), - value: factory.type, - 'data-test-subj': `createNew-${factory.type}`, - })), - ]; - - return list; + private toggleCreateMenu = () => { + this.setState(prevState => ({ isCreateMenuOpen: !prevState.isCreateMenuOpen })); + }; + + private closeCreateMenu = () => { + this.setState({ isCreateMenuOpen: false }); + }; + + private getCreateMenuItems() { + return [...this.props.getAllFactories()] + .filter(factory => factory.isEditable() && !factory.isContainerType && factory.canCreateNew()) + .map(factory => ( + this.createNewEmbeddable(factory.type)} + className="embPanel__addItem" + > + {capitalize(factory.getDisplayName())} + + )); } public render() { @@ -150,6 +147,7 @@ export class AddPanelFlyout extends React.Component { })} /> ); + return ( @@ -161,16 +159,28 @@ export class AddPanelFlyout extends React.Component { {savedObjectsFinder} - - - this.createNewEmbeddable(value)} - /> - - + iconType="arrowDown" + iconSide="right" + onClick={this.toggleCreateMenu} + > + + + } + isOpen={this.state.isCreateMenuOpen} + closePopover={this.closeCreateMenu} + panelPaddingSize="none" + anchorPosition="upLeft" + > + + ); 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 550f9706a634b5..8d9beec940acc0 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 @@ -28,7 +28,6 @@ import { } from '../../../test_samples'; // eslint-disable-next-line import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; -import { FilterStateStore } from '@kbn/es-query'; import { EmbeddableFactory, EmbeddableOutput, @@ -37,6 +36,7 @@ import { } from '../../../embeddables'; import { GetEmbeddableFactory } from '../../../types'; import { of } from '../../../../tests/helpers'; +import { esFilters } from '../../../../../../../plugins/data/public'; const setup = async () => { const embeddableFactories = new Map(); @@ -48,7 +48,7 @@ const setup = async () => { panels: {}, filters: [ { - $state: { store: FilterStateStore.APP_STATE }, + $state: { store: esFilters.FilterStateStore.APP_STATE }, 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 22e3be89f1ae93..684a8c45a4e890 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 @@ -17,7 +17,6 @@ * under the License. */ -import { Filter, FilterStateStore } from '@kbn/es-query'; import { EmbeddableOutput, isErrorEmbeddable } from '../../../'; import { RemovePanelAction } from './remove_panel_action'; import { EmbeddableFactory } from '../../../embeddables'; @@ -30,6 +29,7 @@ import { FilterableEmbeddableFactory } from '../../../test_samples/embeddables/f import { FilterableContainer } from '../../../test_samples/embeddables/filterable_container'; import { GetEmbeddableFactory, ViewMode } from '../../../types'; import { ContactCardEmbeddable } from '../../../test_samples/embeddables/contact_card/contact_card_embeddable'; +import { esFilters } from '../../../../../../../plugins/data/public'; const embeddableFactories = new Map(); embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory()); @@ -39,8 +39,8 @@ let container: FilterableContainer; let embeddable: FilterableEmbeddable; beforeEach(async () => { - const derivedFilter: Filter = { - $state: { store: FilterStateStore.APP_STATE }, + const derivedFilter: esFilters.Filter = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; 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 eaef8048a6fbff..de708b778c3c7c 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 @@ -16,14 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { Filter } from '@kbn/es-query'; + import { Container, ContainerInput } from '../../containers'; import { GetEmbeddableFactory } from '../../types'; +import { esFilters } from '../../../../../data/public'; export const FILTERABLE_CONTAINER = 'FILTERABLE_CONTAINER'; export interface FilterableContainerInput extends ContainerInput { - filters: Filter[]; + filters: esFilters.Filter[]; } /** @@ -33,7 +34,7 @@ export interface FilterableContainerInput extends ContainerInput { */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type InheritedChildrenInput = { - filters: Filter[]; + filters: esFilters.Filter[]; 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 f6885ca25b437a..56aa7688f37a69 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 @@ -17,14 +17,14 @@ * under the License. */ -import { Filter } from '@kbn/es-query'; import { IContainer } from '../../containers'; import { EmbeddableOutput, EmbeddableInput, Embeddable } from '../../embeddables'; +import { esFilters } from '../../../../../data/public'; export const FILTERABLE_EMBEDDABLE = 'FILTERABLE_EMBEDDABLE'; export interface FilterableEmbeddableInput extends EmbeddableInput { - filters: Filter[]; + filters: esFilters.Filter[]; } export class FilterableEmbeddable extends Embeddable { diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts index 52500acc3dc59d..0721acb1a1fba9 100644 --- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts +++ b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts @@ -32,7 +32,7 @@ import { } from '../lib/test_samples'; // eslint-disable-next-line import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; -import { FilterStateStore } from '@kbn/es-query'; +import { esFilters } from '../../../../plugins/data/public'; test('ApplyFilterAction applies the filter to the root of the container tree', async () => { const { doStart } = testPlugin(); @@ -76,7 +76,7 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a } const filter: any = { - $state: { store: FilterStateStore.APP_STATE }, + $state: { store: esFilters.FilterStateStore.APP_STATE }, meta: { disabled: false, negate: false, diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index 3bdbcbad857d65..f97c26a41b901b 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -26,7 +26,6 @@ import { FILTERABLE_EMBEDDABLE, } from '../lib/test_samples/embeddables/filterable_embeddable'; import { ERROR_EMBEDDABLE_TYPE } from '../lib/embeddables/error_embeddable'; -import { Filter, FilterStateStore } from '@kbn/es-query'; import { FilterableEmbeddableFactory } from '../lib/test_samples/embeddables/filterable_embeddable_factory'; import { CONTACT_CARD_EMBEDDABLE } from '../lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory'; import { SlowContactCardEmbeddableFactory } from '../lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory'; @@ -46,6 +45,7 @@ import { import { coreMock } from '../../../../core/public/mocks'; import { testPlugin } from './test_plugin'; import { of } from './helpers'; +import { esFilters } from '../../../../plugins/data/public'; async function creatHelloWorldContainerAndEmbeddable( containerInput: ContainerInput = { id: 'hello', panels: {} }, @@ -437,8 +437,8 @@ test('Test nested reactions', async done => { test('Explicit embeddable input mapped to undefined will default to inherited', async () => { const { start } = await creatHelloWorldContainerAndEmbeddable(); - const derivedFilter: Filter = { - $state: { store: FilterStateStore.APP_STATE }, + const derivedFilter: esFilters.Filter = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, 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 6cde7bdc48ba17..47c4b0944cef22 100644 --- a/src/plugins/embeddable/public/tests/explicit_input.test.ts +++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts @@ -18,7 +18,6 @@ */ import { skip } from 'rxjs/operators'; -import { Filter, FilterStateStore } from '@kbn/es-query'; import { testPlugin } from './test_plugin'; import { FILTERABLE_EMBEDDABLE, @@ -34,6 +33,7 @@ import { isErrorEmbeddable } from '../lib'; import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world_container'; // eslint-disable-next-line import { coreMock } from '../../../../core/public/mocks'; +import { esFilters } from '../../../../plugins/data/public'; const { setup, doStart, coreStart, uiActions } = testPlugin( coreMock.createSetup(), @@ -50,8 +50,8 @@ setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, factory); setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE_TYPE, new HelloWorldEmbeddableFactory()); test('Explicit embeddable input mapped to undefined will default to inherited', async () => { - const derivedFilter: Filter = { - $state: { store: FilterStateStore.APP_STATE }, + const derivedFilter: esFilters.Filter = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/expressions/common/expressions/expression_types/index.ts b/src/plugins/expressions/common/expressions/expression_types/index.ts deleted file mode 100644 index bb8a554487f017..00000000000000 --- a/src/plugins/expressions/common/expressions/expression_types/index.ts +++ /dev/null @@ -1,64 +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 { boolean } from './boolean'; -import { datatable } from './datatable'; -import { error } from './error'; -import { filter } from './filter'; -import { image } from './image'; -import { nullType } from './null'; -import { number } from './number'; -import { pointseries } from './pointseries'; -import { range } from './range'; -import { render } from './render'; -import { shape } from './shape'; -import { string } from './string'; -import { style } from './style'; -import { kibanaContext } from './kibana_context'; -import { kibanaDatatable } from './kibana_datatable'; - -export const typeSpecs = [ - boolean, - datatable, - error, - filter, - image, - number, - nullType, - pointseries, - range, - render, - shape, - string, - style, - kibanaContext, - kibanaDatatable, -]; - -// Types -export * from './datatable'; -export * from './error'; -export * from './filter'; -export * from './image'; -export * from './kibana_context'; -export * from './kibana_datatable'; -export * from './pointseries'; -export * from './render'; -export * from './style'; -export * from './range'; diff --git a/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts b/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts deleted file mode 100644 index 174517abc2c053..00000000000000 --- a/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts +++ /dev/null @@ -1,49 +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 { Filter } from '@kbn/es-query'; -import { TimeRange, Query } from 'src/plugins/data/public'; - -const name = 'kibana_context'; -export type KIBANA_CONTEXT_NAME = 'kibana_context'; - -export interface KibanaContext { - type: typeof name; - query?: Query | Query[]; - filters?: Filter[]; - timeRange?: TimeRange; -} - -export const kibanaContext = () => ({ - name, - from: { - null: () => { - return { - type: name, - }; - }, - }, - to: { - null: () => { - return { - type: 'null', - }; - }, - }, -}); diff --git a/src/plugins/expressions/common/expressions/expression_types/range.ts b/src/plugins/expressions/common/expressions/expression_types/range.ts deleted file mode 100644 index c1a0e4d2075fa8..00000000000000 --- a/src/plugins/expressions/common/expressions/expression_types/range.ts +++ /dev/null @@ -1,51 +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 { ExpressionType, Render } from '../../../common/expressions/types'; - -const name = 'range'; - -export interface Range { - type: typeof name; - from: number; - to: number; -} - -export const range = (): ExpressionType => ({ - name, - from: { - null: (): Range => { - return { - type: 'range', - from: 0, - to: 0, - }; - }, - }, - to: { - render: (value: Range): Render<{ text: string }> => { - const text = `from ${value.from} to ${value.to}`; - return { - type: 'render', - as: 'text', - value: { text }, - }; - }, - }, -}); diff --git a/src/plugins/expressions/common/expressions/expression_types/style.ts b/src/plugins/expressions/common/expressions/expression_types/style.ts deleted file mode 100644 index f6ef0f1fe42e6a..00000000000000 --- a/src/plugins/expressions/common/expressions/expression_types/style.ts +++ /dev/null @@ -1,41 +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 { ExpressionType } from '../types'; - -const name = 'style'; - -export interface Style { - type: typeof name; - spec: any; - css: string; -} - -export const style = (): ExpressionType => ({ - name, - from: { - null: () => { - return { - type: 'style', - spec: {}, - css: '', - }; - }, - }, -}); diff --git a/src/plugins/expressions/common/expressions/index.ts b/src/plugins/expressions/common/expressions/index.ts deleted file mode 100644 index 8c8dd7eb26ca59..00000000000000 --- a/src/plugins/expressions/common/expressions/index.ts +++ /dev/null @@ -1,23 +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. - */ - -export * from './types'; -export { Type } from './interpreter'; -export { interpreterProvider } from './interpreter_provider'; -export { serializeProvider, getType } from './serialize_provider'; diff --git a/src/plugins/expressions/common/expressions/interpreter.ts b/src/plugins/expressions/common/expressions/interpreter.ts deleted file mode 100644 index 07146b6eb31810..00000000000000 --- a/src/plugins/expressions/common/expressions/interpreter.ts +++ /dev/null @@ -1,71 +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 { get } from 'lodash'; - -function getType(node: any) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; - } - return typeof node; -} - -export function Type(this: any, config: any) { - // Required - this.name = config.name; - - // Optional - this.help = config.help || ''; // A short help text - - // Optional type validation, useful for checking function output - this.validate = config.validate || function validate() {}; - - // Optional - this.create = config.create; - - // Optional serialization (used when passing context around client/server) - this.serialize = config.serialize; - this.deserialize = config.deserialize; - - const getToFn = (type: any) => get(config, ['to', type]) || get(config, ['to', '*']); - const getFromFn = (type: any) => get(config, ['from', type]) || get(config, ['from', '*']); - - this.castsTo = (type: any) => typeof getToFn(type) === 'function'; - this.castsFrom = (type: any) => typeof getFromFn(type) === 'function'; - - this.to = (node: any, toTypeName: any, types: any) => { - const typeName = getType(node); - if (typeName !== this.name) { - throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`); - } else if (!this.castsTo(toTypeName)) { - throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`); - } - - return (getToFn(toTypeName) as any)(node, types); - }; - - this.from = (node: any, types: any) => { - const typeName = getType(node); - if (!this.castsFrom(typeName)) throw new Error(`Can not cast '${this.name}' from ${typeName}`); - - return (getFromFn(typeName) as any)(node, types); - }; -} diff --git a/src/plugins/expressions/common/expressions/serialize_provider.test.ts b/src/plugins/expressions/common/expressions/serialize_provider.test.ts deleted file mode 100644 index 774fb34938dd2c..00000000000000 --- a/src/plugins/expressions/common/expressions/serialize_provider.test.ts +++ /dev/null @@ -1,48 +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 { getType } from './serialize_provider'; - -describe('getType()', () => { - test('returns "null" string for null or undefined', () => { - expect(getType(null)).toBe('null'); - expect(getType(undefined)).toBe('null'); - }); - - test('returns basic type name', () => { - expect(getType(0)).toBe('number'); - expect(getType(1)).toBe('number'); - expect(getType(0.8)).toBe('number'); - expect(getType(Infinity)).toBe('number'); - - expect(getType(true)).toBe('boolean'); - expect(getType(false)).toBe('boolean'); - }); - - test('returns .type property value of objects', () => { - expect(getType({ type: 'foo' })).toBe('foo'); - expect(getType({ type: 'bar' })).toBe('bar'); - }); - - test('throws if object has no .type property', () => { - expect(() => getType({})).toThrow(); - expect(() => getType({ _type: 'foo' })).toThrow(); - expect(() => getType({ tipe: 'foo' })).toThrow(); - }); -}); diff --git a/src/plugins/expressions/common/expressions/types/index.ts b/src/plugins/expressions/common/expressions/types/index.ts deleted file mode 100644 index baa0850cfd76cb..00000000000000 --- a/src/plugins/expressions/common/expressions/types/index.ts +++ /dev/null @@ -1,45 +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. - */ - -export { ArgumentType } from './arguments'; -export { - TypeToString, - KnownTypeToString, - TypeString, - UnmappedTypeStrings, - UnwrapPromise, -} from './common'; -export { ExpressionFunction, AnyExpressionFunction, FunctionHandlers } from './functions'; -export { ExpressionType, AnyExpressionType } from './types'; -export * from '../expression_types'; - -export type ExpressionArgAST = string | boolean | number | ExpressionAST; - -export interface ExpressionFunctionAST { - type: 'function'; - function: string; - arguments: { - [key: string]: ExpressionArgAST[]; - }; -} - -export interface ExpressionAST { - type: 'expression'; - chain: ExpressionFunctionAST[]; -} diff --git a/src/plugins/expressions/common/expressions/types/types.ts b/src/plugins/expressions/common/expressions/types/types.ts deleted file mode 100644 index 59297e922d313d..00000000000000 --- a/src/plugins/expressions/common/expressions/types/types.ts +++ /dev/null @@ -1,56 +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. - */ - -export type ExpressionValueUnboxed = any; - -export type ExpressionValueBoxed = { - type: Type; -} & Value; - -export type ExpressionValue = ExpressionValueUnboxed | ExpressionValueBoxed; - -export type ExpressionValueConverter = ( - input: I, - availableTypes: Record -) => O; - -/** - * A generic type which represents a custom Expression Type Definition that's - * registered to the Interpreter. - */ -export interface ExpressionType< - Name extends string, - Value extends ExpressionValueUnboxed | ExpressionValueBoxed, - SerializedType = undefined -> { - name: Name; - validate?: (type: any) => void | Error; - serialize?: (type: Value) => SerializedType; - deserialize?: (type: SerializedType) => Value; - // TODO: Update typings for the `availableTypes` parameter once interfaces for this - // have been added elsewhere in the interpreter. - from?: { - [type: string]: ExpressionValueConverter; - }; - to?: { - [type: string]: ExpressionValueConverter; - }; -} - -export type AnyExpressionType = ExpressionType; diff --git a/src/plugins/expressions/common/index.ts b/src/plugins/expressions/common/index.ts deleted file mode 100644 index af870208f4865c..00000000000000 --- a/src/plugins/expressions/common/index.ts +++ /dev/null @@ -1,20 +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. - */ - -export * from './expressions'; diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json index ce2b1854cf4158..ec87b56f3745ed 100644 --- a/src/plugins/expressions/kibana.json +++ b/src/plugins/expressions/kibana.json @@ -2,5 +2,8 @@ "id": "expressions", "version": "kibana", "server": false, - "ui": true + "ui": true, + "requiredPlugins": [ + "inspector" + ] } diff --git a/src/plugins/expressions/common/expressions/create_error.ts b/src/plugins/expressions/public/create_error.ts similarity index 100% rename from src/plugins/expressions/common/expressions/create_error.ts rename to src/plugins/expressions/public/create_error.ts diff --git a/src/plugins/expressions/public/create_handlers.ts b/src/plugins/expressions/public/create_handlers.ts new file mode 100644 index 00000000000000..46e85411c58956 --- /dev/null +++ b/src/plugins/expressions/public/create_handlers.ts @@ -0,0 +1,24 @@ +/* + * 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 function createHandlers() { + return { + environment: 'client', + }; +} diff --git a/src/plugins/expressions/common/expressions/expression_types/boolean.ts b/src/plugins/expressions/public/expression_types/boolean.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/boolean.ts rename to src/plugins/expressions/public/expression_types/boolean.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/datatable.ts b/src/plugins/expressions/public/expression_types/datatable.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/datatable.ts rename to src/plugins/expressions/public/expression_types/datatable.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/error.ts b/src/plugins/expressions/public/expression_types/error.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/error.ts rename to src/plugins/expressions/public/expression_types/error.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/filter.ts b/src/plugins/expressions/public/expression_types/filter.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/filter.ts rename to src/plugins/expressions/public/expression_types/filter.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/image.ts b/src/plugins/expressions/public/expression_types/image.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/image.ts rename to src/plugins/expressions/public/expression_types/image.ts diff --git a/src/plugins/expressions/public/expression_types/index.ts b/src/plugins/expressions/public/expression_types/index.ts new file mode 100644 index 00000000000000..a5d182fee75edd --- /dev/null +++ b/src/plugins/expressions/public/expression_types/index.ts @@ -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 { boolean } from './boolean'; +import { datatable } from './datatable'; +import { error } from './error'; +import { filter } from './filter'; +import { image } from './image'; +import { kibanaContext } from './kibana_context'; +import { kibanaDatatable } from './kibana_datatable'; +import { nullType } from './null'; +import { number } from './number'; +import { pointseries } from './pointseries'; +import { range } from './range'; +import { render } from './render'; +import { shape } from './shape'; +import { string } from './string'; +import { style } from './style'; + +export const typeSpecs = [ + boolean, + datatable, + error, + filter, + image, + kibanaContext, + kibanaDatatable, + nullType, + number, + pointseries, + range, + render, + shape, + string, + style, +]; + +export * from './boolean'; +export * from './datatable'; +export * from './error'; +export * from './filter'; +export * from './image'; +export * from './kibana_context'; +export * from './kibana_datatable'; +export * from './null'; +export * from './number'; +export * from './pointseries'; +export * from './range'; +export * from './render'; +export * from './shape'; +export * from './string'; +export * from './style'; diff --git a/src/plugins/expressions/public/expression_types/kibana_context.ts b/src/plugins/expressions/public/expression_types/kibana_context.ts new file mode 100644 index 00000000000000..bcf8e2853dec88 --- /dev/null +++ b/src/plugins/expressions/public/expression_types/kibana_context.ts @@ -0,0 +1,48 @@ +/* + * 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 { TimeRange, Query, esFilters } from 'src/plugins/data/public'; + +const name = 'kibana_context'; +export type KIBANA_CONTEXT_NAME = 'kibana_context'; + +export interface KibanaContext { + type: typeof name; + query?: Query | Query[]; + filters?: esFilters.Filter[]; + timeRange?: TimeRange; +} + +export const kibanaContext = () => ({ + name, + from: { + null: () => { + return { + type: name, + }; + }, + }, + to: { + null: () => { + return { + type: 'null', + }; + }, + }, +}); diff --git a/src/plugins/expressions/common/expressions/expression_types/kibana_datatable.ts b/src/plugins/expressions/public/expression_types/kibana_datatable.ts similarity index 97% rename from src/plugins/expressions/common/expressions/expression_types/kibana_datatable.ts rename to src/plugins/expressions/public/expression_types/kibana_datatable.ts index 7f77e226ff1d9d..c360a2be8c7f7b 100644 --- a/src/plugins/expressions/common/expressions/expression_types/kibana_datatable.ts +++ b/src/plugins/expressions/public/expression_types/kibana_datatable.ts @@ -19,7 +19,7 @@ import { map } from 'lodash'; import { SerializedFieldFormat } from '../types/common'; -import { Datatable, PointSeries } from '../types'; +import { Datatable, PointSeries } from '.'; const name = 'kibana_datatable'; diff --git a/src/plugins/expressions/common/expressions/expression_types/null.ts b/src/plugins/expressions/public/expression_types/null.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/null.ts rename to src/plugins/expressions/public/expression_types/null.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/number.ts b/src/plugins/expressions/public/expression_types/number.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/number.ts rename to src/plugins/expressions/public/expression_types/number.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/pointseries.ts b/src/plugins/expressions/public/expression_types/pointseries.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/pointseries.ts rename to src/plugins/expressions/public/expression_types/pointseries.ts diff --git a/src/plugins/expressions/public/expression_types/range.ts b/src/plugins/expressions/public/expression_types/range.ts new file mode 100644 index 00000000000000..082056c909988b --- /dev/null +++ b/src/plugins/expressions/public/expression_types/range.ts @@ -0,0 +1,52 @@ +/* + * 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 { ExpressionType } from '../types'; +import { Render } from '.'; + +const name = 'range'; + +export interface Range { + type: typeof name; + from: number; + to: number; +} + +export const range = (): ExpressionType => ({ + name, + from: { + null: (): Range => { + return { + type: 'range', + from: 0, + to: 0, + }; + }, + }, + to: { + render: (value: Range): Render<{ text: string }> => { + const text = `from ${value.from} to ${value.to}`; + return { + type: 'render', + as: 'text', + value: { text }, + }; + }, + }, +}); diff --git a/src/plugins/expressions/common/expressions/expression_types/render.ts b/src/plugins/expressions/public/expression_types/render.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/render.ts rename to src/plugins/expressions/public/expression_types/render.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/shape.ts b/src/plugins/expressions/public/expression_types/shape.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/shape.ts rename to src/plugins/expressions/public/expression_types/shape.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/string.ts b/src/plugins/expressions/public/expression_types/string.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/string.ts rename to src/plugins/expressions/public/expression_types/string.ts diff --git a/src/plugins/expressions/public/expression_types/style.ts b/src/plugins/expressions/public/expression_types/style.ts new file mode 100644 index 00000000000000..d93893d25c11cf --- /dev/null +++ b/src/plugins/expressions/public/expression_types/style.ts @@ -0,0 +1,35 @@ +/* + * 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 { ExpressionType, ExpressionTypeStyle } from '../types'; + +const name = 'style'; + +export const style = (): ExpressionType => ({ + name, + from: { + null: () => { + return { + type: 'style', + spec: {}, + css: '', + }; + }, + }, +}); diff --git a/src/plugins/expressions/public/expressions/expressions_service.ts b/src/plugins/expressions/public/expressions/expressions_service.ts deleted file mode 100644 index 4b6820143b794b..00000000000000 --- a/src/plugins/expressions/public/expressions/expressions_service.ts +++ /dev/null @@ -1,69 +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 { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './interpreter'; -import { AnyExpressionType, AnyExpressionFunction } from '../../common/expressions/types'; - -export interface ExpressionsSetupContract { - registerFunction: (fn: () => AnyExpressionFunction) => void; - registerRenderer: (renderer: any) => void; - registerType: (type: () => AnyExpressionType) => void; - __LEGACY: { - functions: FunctionsRegistry; - renderers: RenderFunctionsRegistry; - types: TypesRegistry; - }; -} - -export type ExpressionsStartContract = ExpressionsSetupContract; - -export class ExpressionsService { - private readonly functions = new FunctionsRegistry(); - private readonly renderers = new RenderFunctionsRegistry(); - private readonly types = new TypesRegistry(); - - private setupApi!: ExpressionsSetupContract; - - public setup() { - const { functions, renderers, types } = this; - - this.setupApi = { - registerFunction: fn => { - this.functions.register(fn); - }, - registerRenderer: (renderer: any) => { - this.renderers.register(renderer); - }, - registerType: type => { - this.types.register(type); - }, - __LEGACY: { - functions, - renderers, - types, - }, - }; - - return this.setupApi; - } - - public start(): ExpressionsStartContract { - return this.setupApi as ExpressionsStartContract; - } -} diff --git a/src/plugins/expressions/public/expressions/interpreter.ts b/src/plugins/expressions/public/expressions/interpreter.ts deleted file mode 100644 index f27ef57c7980ac..00000000000000 --- a/src/plugins/expressions/public/expressions/interpreter.ts +++ /dev/null @@ -1,173 +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. - */ - -/** - * @todo - * This whole file needs major refactoring. `Registry` class does not do anything - * useful. "Wrappers" like `RenderFunction` basically just set default props on the objects. - */ - -/* eslint-disable max-classes-per-file */ -import { clone, mapValues, includes } from 'lodash'; -import { Type } from '../../common/expressions/interpreter'; -import { ExpressionType, AnyExpressionFunction } from '../../common/expressions/types'; - -export class Registry { - _prop: string; - _indexed: any; - - constructor(prop: string = 'name') { - if (typeof prop !== 'string') throw new Error('Registry property name must be a string'); - this._prop = prop; - this._indexed = new Object(); - } - - wrapper(obj: ItemSpec) { - return obj; - } - - register(fn: () => ItemSpec) { - if (typeof fn !== 'function') throw new Error(`Register requires an function`); - - const obj = fn() as any; - - if (typeof obj !== 'object' || !obj[this._prop]) { - throw new Error(`Registered functions must return an object with a ${this._prop} property`); - } - - this._indexed[obj[this._prop].toLowerCase()] = this.wrapper(obj); - } - - toJS(): Record { - return Object.keys(this._indexed).reduce( - (acc, key) => { - acc[key] = this.get(key); - return acc; - }, - {} as any - ); - } - - toArray(): Item[] { - return Object.keys(this._indexed).map(key => this.get(key)!); - } - - get(name: string): Item | null { - if (name === undefined) { - return null; - } - const lowerCaseName = name.toLowerCase(); - return this._indexed[lowerCaseName] ? clone(this._indexed[lowerCaseName]) : null; - } - - getProp(): string { - return this._prop; - } - - reset() { - this._indexed = new Object(); - } -} - -function RenderFunction(this: any, config: any) { - // This must match the name of the function that is used to create the `type: render` object - this.name = config.name; - - // Use this to set a more friendly name - this.displayName = config.displayName || this.name; - - // A sentence or few about what this element does - this.help = config.help; - - // used to validate the data before calling the render function - this.validate = config.validate || function validate() {}; - - // tell the renderer if the dom node should be reused, it's recreated each time by default - this.reuseDomNode = Boolean(config.reuseDomNode); - - // the function called to render the data - this.render = - config.render || - function render(domNode: any, data: any, done: any) { - done(); - }; -} - -export function Arg(this: any, config: any) { - if (config.name === '_') throw Error('Arg names must not be _. Use it in aliases instead.'); - this.name = config.name; - this.required = config.required || false; - this.help = config.help || ''; - this.types = config.types || []; - this.default = config.default; - this.aliases = config.aliases || []; - this.multi = config.multi == null ? false : config.multi; - this.resolve = config.resolve == null ? true : config.resolve; - this.options = config.options || []; - this.accepts = (type: any) => { - if (!this.types.length) return true; - return includes(config.types, type); - }; -} - -export function Fn(this: any, config: any) { - // Required - this.name = config.name; // Name of function - - // Return type of function. - // This SHOULD be supplied. We use it for UI and autocomplete hinting, - // We may also use it for optimizations in the future. - this.type = config.type; - this.aliases = config.aliases || []; - - // Function to run function (context, args) - this.fn = (...args: any) => Promise.resolve(config.fn(...args)); - - // Optional - this.help = config.help || ''; // A short help text - this.args = mapValues( - config.args || {}, - (arg: any, name: any) => new (Arg as any)({ name, ...arg }) - ); - - this.context = config.context || {}; - - this.accepts = (type: any) => { - if (!this.context.types) return true; // If you don't tell us about context, we'll assume you don't care what you get - return includes(this.context.types, type); // Otherwise, check it - }; -} - -export class RenderFunctionsRegistry extends Registry { - wrapper(obj: any) { - return new (RenderFunction as any)(obj); - } -} - -export class FunctionsRegistry extends Registry { - wrapper(obj: any) { - return new (Fn as any)(obj); - } -} - -export class TypesRegistry extends Registry, any> { - wrapper(obj: any) { - return new (Type as any)(obj); - } -} diff --git a/src/plugins/expressions/public/fonts.ts b/src/plugins/expressions/public/fonts.ts new file mode 100644 index 00000000000000..cdf3d4c16f3b57 --- /dev/null +++ b/src/plugins/expressions/public/fonts.ts @@ -0,0 +1,151 @@ +/* + * 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. + */ + +/** + * This type contains a unions of all supported font labels, or the the name of + * the font the user would see in a UI. + */ +export type FontLabel = typeof fonts[number]['label']; + +/** + * This type contains a union of all supported font values, equivalent to the CSS + * `font-value` property. + */ +export type FontValue = typeof fonts[number]['value']; + +/** + * An interface representing a font in Canvas, with a textual label and the CSS + * `font-value`. + */ +export interface Font { + label: FontLabel; + value: FontValue; +} + +// This function allows one to create a strongly-typed font for inclusion in +// the font collection. As a result, the values and labels are known to the +// type system, preventing one from specifying a non-existent font at build +// time. +function createFont< + RawFont extends { value: RawFontValue; label: RawFontLabel }, + RawFontValue extends string, + RawFontLabel extends string +>(font: RawFont) { + return font; +} + +export const americanTypewriter = createFont({ + label: 'American Typewriter', + value: "'American Typewriter', 'Courier New', Courier, Monaco, mono", +}); + +export const arial = createFont({ label: 'Arial', value: 'Arial, sans-serif' }); + +export const baskerville = createFont({ + label: 'Baskerville', + value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +export const bookAntiqua = createFont({ + label: 'Book Antiqua', + value: "'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +export const brushScript = createFont({ + label: 'Brush Script', + value: "'Brush Script MT', 'Comic Sans', sans-serif", +}); + +export const chalkboard = createFont({ + label: 'Chalkboard', + value: "Chalkboard, 'Comic Sans', sans-serif", +}); + +export const didot = createFont({ + label: 'Didot', + value: "Didot, Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +export const futura = createFont({ + label: 'Futura', + value: 'Futura, Impact, Helvetica, Arial, sans-serif', +}); + +export const gillSans = createFont({ + label: 'Gill Sans', + value: + "'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", +}); + +export const helveticaNeue = createFont({ + label: 'Helvetica Neue', + value: "'Helvetica Neue', Helvetica, Arial, sans-serif", +}); + +export const hoeflerText = createFont({ + label: 'Hoefler Text', + value: "'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif", +}); + +export const lucidaGrande = createFont({ + label: 'Lucida Grande', + value: "'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif", +}); + +export const myriad = createFont({ + label: 'Myriad', + value: 'Myriad, Helvetica, Arial, sans-serif', +}); + +export const openSans = createFont({ + label: 'Open Sans', + value: "'Open Sans', Helvetica, Arial, sans-serif", +}); + +export const optima = createFont({ + label: 'Optima', + value: "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", +}); + +export const palatino = createFont({ + label: 'Palatino', + value: "Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +/** + * A collection of supported fonts. + */ +export const fonts = [ + americanTypewriter, + arial, + baskerville, + bookAntiqua, + brushScript, + chalkboard, + didot, + futura, + gillSans, + helveticaNeue, + hoeflerText, + lucidaGrande, + myriad, + openSans, + optima, + palatino, +]; diff --git a/src/plugins/expressions/public/functions/clog.ts b/src/plugins/expressions/public/functions/clog.ts new file mode 100644 index 00000000000000..929075b882b964 --- /dev/null +++ b/src/plugins/expressions/public/functions/clog.ts @@ -0,0 +1,35 @@ +/* + * 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 { ExpressionFunction } from '../types'; + +const name = 'clog'; + +type Context = any; +type ClogExpressionFunction = ExpressionFunction; + +export const clog = (): ClogExpressionFunction => ({ + name, + args: {}, + help: 'Outputs the context to the console', + fn: context => { + console.log(context); // eslint-disable-line no-console + return context; + }, +}); diff --git a/src/plugins/expressions/public/functions/font.ts b/src/plugins/expressions/public/functions/font.ts new file mode 100644 index 00000000000000..33e28924f3ee1f --- /dev/null +++ b/src/plugins/expressions/public/functions/font.ts @@ -0,0 +1,186 @@ +/* + * 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 { openSans, FontLabel as FontFamily } from '../fonts'; +import { ExpressionFunction } from '../types'; +import { CSSStyle, FontStyle, FontWeight, Style, TextAlignment, TextDecoration } from '../types'; + +const dashify = (str: string) => { + return str + .trim() + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/\W/g, m => (/[À-ž]/.test(m) ? m : '-')) + .replace(/^-+|-+$/g, '') + .toLowerCase(); +}; + +const inlineStyle = (obj: Record) => { + if (!obj) return ''; + const styles = Object.keys(obj).map(key => { + const prop = dashify(key); + const line = prop.concat(':').concat(String(obj[key])); + return line; + }); + return styles.join(';'); +}; + +interface Arguments { + align?: TextAlignment; + color?: string; + family?: FontFamily; + italic?: boolean; + lHeight?: number | null; + size?: number; + underline?: boolean; + weight?: FontWeight; +} + +export function font(): ExpressionFunction<'font', null, Arguments, Style> { + return { + name: 'font', + aliases: [], + type: 'style', + help: i18n.translate('expressions_np.functions.fontHelpText', { + defaultMessage: 'Create a font style.', + }), + context: { + types: ['null'], + }, + args: { + align: { + default: 'left', + help: i18n.translate('expressions_np.functions.font.args.alignHelpText', { + defaultMessage: 'The horizontal text alignment.', + }), + options: Object.values(TextAlignment), + types: ['string'], + }, + color: { + help: i18n.translate('expressions_np.functions.font.args.colorHelpText', { + defaultMessage: 'The text color.', + }), + types: ['string'], + }, + family: { + default: `"${openSans.value}"`, + help: i18n.translate('expressions_np.functions.font.args.familyHelpText', { + defaultMessage: 'An acceptable {css} web font string', + values: { + css: 'CSS', + }, + }), + types: ['string'], + }, + italic: { + default: false, + help: i18n.translate('expressions_np.functions.font.args.italicHelpText', { + defaultMessage: 'Italicize the text?', + }), + options: [true, false], + types: ['boolean'], + }, + lHeight: { + default: null, + aliases: ['lineHeight'], + help: i18n.translate('expressions_np.functions.font.args.lHeightHelpText', { + defaultMessage: 'The line height in pixels', + }), + types: ['number', 'null'], + }, + size: { + default: 14, + help: i18n.translate('expressions_np.functions.font.args.sizeHelpText', { + defaultMessage: 'The font size in pixels', + }), + types: ['number'], + }, + underline: { + default: false, + help: i18n.translate('expressions_np.functions.font.args.underlineHelpText', { + defaultMessage: 'Underline the text?', + }), + options: [true, false], + types: ['boolean'], + }, + weight: { + default: 'normal', + help: i18n.translate('expressions_np.functions.font.args.weightHelpText', { + defaultMessage: 'The font weight. For example, {list}, or {end}.', + values: { + list: Object.values(FontWeight) + .slice(0, -1) + .map(weight => `\`"${weight}"\``) + .join(', '), + end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, + }, + }), + options: Object.values(FontWeight), + types: ['string'], + }, + }, + fn: (_context, args) => { + if (!Object.values(FontWeight).includes(args.weight!)) { + throw new Error( + i18n.translate('expressions_np.functions.font.invalidFontWeightErrorMessage', { + defaultMessage: "Invalid font weight: '{weight}'", + values: { + weight: args.weight, + }, + }) + ); + } + if (!Object.values(TextAlignment).includes(args.align!)) { + throw new Error( + i18n.translate('expressions_np.functions.font.invalidTextAlignmentErrorMessage', { + defaultMessage: "Invalid text alignment: '{align}'", + values: { + align: args.align, + }, + }) + ); + } + + // the line height shouldn't ever be lower than the size, and apply as a + // pixel setting + const lineHeight = args.lHeight != null ? `${args.lHeight}px` : '1'; + + const spec: CSSStyle = { + fontFamily: args.family, + fontWeight: args.weight, + fontStyle: args.italic ? FontStyle.ITALIC : FontStyle.NORMAL, + textDecoration: args.underline ? TextDecoration.UNDERLINE : TextDecoration.NONE, + textAlign: args.align, + fontSize: `${args.size}px`, // apply font size as a pixel setting + lineHeight, // apply line height as a pixel setting + }; + + // conditionally apply styles based on input + if (args.color) { + spec.color = args.color; + } + + return { + type: 'style', + spec, + css: inlineStyle(spec as Record), + }; + }, + }; +} diff --git a/src/plugins/expressions/public/functions/kibana.ts b/src/plugins/expressions/public/functions/kibana.ts new file mode 100644 index 00000000000000..7ed9619675aead --- /dev/null +++ b/src/plugins/expressions/public/functions/kibana.ts @@ -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 { i18n } from '@kbn/i18n'; +import { ExpressionFunction } from '../types'; +import { KibanaContext } from '../expression_types'; + +export type ExpressionFunctionKibana = ExpressionFunction< + 'kibana', + KibanaContext | null, + object, + KibanaContext +>; + +export const kibana = (): ExpressionFunctionKibana => ({ + name: 'kibana', + type: 'kibana_context', + + context: { + types: ['kibana_context', 'null'], + }, + + help: i18n.translate('expressions_np.functions.kibana.help', { + defaultMessage: 'Gets kibana global context', + }), + args: {}, + fn(context, args, handlers) { + const initialContext = handlers.getInitialContext ? handlers.getInitialContext() : {}; + + if (context && context.query) { + initialContext.query = initialContext.query.concat(context.query); + } + + if (context && context.filters) { + initialContext.filters = initialContext.filters.concat(context.filters); + } + + const timeRange = initialContext.timeRange || (context ? context.timeRange : undefined); + + return { + ...context, + type: 'kibana_context', + query: initialContext.query, + filters: initialContext.filters, + timeRange, + }; + }, +}); diff --git a/src/plugins/expressions/public/functions/kibana_context.ts b/src/plugins/expressions/public/functions/kibana_context.ts new file mode 100644 index 00000000000000..2a0cd09734b6d4 --- /dev/null +++ b/src/plugins/expressions/public/functions/kibana_context.ts @@ -0,0 +1,113 @@ +/* + * 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 { ExpressionFunction } from '../types'; +import { KibanaContext } from '../expression_types'; +import { savedObjects } from '../services'; + +interface Arguments { + q?: string | null; + filters?: string | null; + timeRange?: string | null; + savedSearchId?: string | null; +} + +export type ExpressionFunctionKibanaContext = ExpressionFunction< + 'kibana_context', + KibanaContext | null, + Arguments, + Promise +>; + +export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ + name: 'kibana_context', + type: 'kibana_context', + context: { + types: ['kibana_context', 'null'], + }, + help: i18n.translate('expressions_np.functions.kibana_context.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + q: { + types: ['string', 'null'], + aliases: ['query', '_'], + default: null, + help: i18n.translate('expressions_np.functions.kibana_context.q.help', { + defaultMessage: 'Specify Kibana free form text query', + }), + }, + filters: { + types: ['string', 'null'], + default: '"[]"', + help: i18n.translate('expressions_np.functions.kibana_context.filters.help', { + defaultMessage: 'Specify Kibana generic filters', + }), + }, + timeRange: { + types: ['string', 'null'], + default: null, + help: i18n.translate('expressions_np.functions.kibana_context.timeRange.help', { + defaultMessage: 'Specify Kibana time range filter', + }), + }, + savedSearchId: { + types: ['string', 'null'], + default: null, + help: i18n.translate('expressions_np.functions.kibana_context.savedSearchId.help', { + defaultMessage: 'Specify saved search ID to be used for queries and filters', + }), + }, + }, + async fn(context, args, handlers) { + const queryArg = args.q ? JSON.parse(args.q) : []; + let queries = Array.isArray(queryArg) ? queryArg : [queryArg]; + let filters = args.filters ? JSON.parse(args.filters) : []; + + if (args.savedSearchId) { + const obj = await savedObjects.get('search', args.savedSearchId); + const search = obj.attributes.kibanaSavedObjectMeta as { searchSourceJSON: string }; + const data = JSON.parse(search.searchSourceJSON) as { query: string; filter: any[] }; + queries = queries.concat(data.query); + filters = filters.concat(data.filter); + } + + if (context && context.query) { + queries = queries.concat(context.query); + } + + if (context && context.filters) { + filters = filters.concat(context.filters).filter((f: any) => !f.meta.disabled); + } + + const timeRange = args.timeRange + ? JSON.parse(args.timeRange) + : context + ? context.timeRange + : undefined; + + return { + type: 'kibana_context', + query: queries, + filters, + timeRange, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/__snapshots__/kibana.test.ts.snap b/src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/interpreter/public/functions/__snapshots__/kibana.test.ts.snap rename to src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap diff --git a/src/legacy/core_plugins/interpreter/public/functions/font.test.ts b/src/plugins/expressions/public/functions/tests/font.test.ts similarity index 97% rename from src/legacy/core_plugins/interpreter/public/functions/font.test.ts rename to src/plugins/expressions/public/functions/tests/font.test.ts index 7032e042a59ea4..a0397b920f9054 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/font.test.ts +++ b/src/plugins/expressions/public/functions/tests/font.test.ts @@ -17,9 +17,9 @@ * under the License. */ -import { openSans } from '../../common/lib/fonts'; -import { font } from './font'; -import { functionWrapper } from '../../test_helpers'; +import { openSans } from '../../fonts'; +import { font } from '../font'; +import { functionWrapper } from './utils'; describe('font', () => { const fn = functionWrapper(font); diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana.test.ts b/src/plugins/expressions/public/functions/tests/kibana.test.ts similarity index 93% rename from src/legacy/core_plugins/interpreter/public/functions/kibana.test.ts rename to src/plugins/expressions/public/functions/tests/kibana.test.ts index cd67825b534cd4..d9489d724cc70b 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana.test.ts +++ b/src/plugins/expressions/public/functions/tests/kibana.test.ts @@ -17,9 +17,10 @@ * under the License. */ -import { functionWrapper } from '../../test_helpers'; -import { kibana } from './kibana'; -import { KibanaContext, FunctionHandlers } from '../../types'; +import { functionWrapper } from './utils'; +import { kibana } from '../kibana'; +import { FunctionHandlers } from '../../types'; +import { KibanaContext } from '../../expression_types'; describe('interpreter/functions#kibana', () => { const fn = functionWrapper(kibana); diff --git a/src/plugins/expressions/public/functions/tests/utils.ts b/src/plugins/expressions/public/functions/tests/utils.ts new file mode 100644 index 00000000000000..5eb9c1a0d6390f --- /dev/null +++ b/src/plugins/expressions/public/functions/tests/utils.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 { mapValues } from 'lodash'; +import { AnyExpressionFunction, FunctionHandlers } from '../../types'; + +// Takes a function spec and passes in default args, +// overriding with any provided args. +export const functionWrapper = (fnSpec: () => T) => { + const spec = fnSpec(); + const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); + return ( + context: object | null, + args: Record = {}, + handlers: FunctionHandlers = {} + ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); +}; diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 21f89db140e5a3..84ad2550fda0ad 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -26,4 +26,9 @@ export function plugin(initializerContext: PluginInitializerContext) { export { ExpressionsPublicPlugin as Plugin }; -export * from '../common'; +export * from './plugin'; +export * from './types'; +export { Type, getType } from './interpreter'; +export { interpreterProvider, ExpressionInterpret } from './interpreter_provider'; +export * from './serialize_provider'; +export * from './expression_types'; diff --git a/src/plugins/expressions/public/interpreter.test.ts b/src/plugins/expressions/public/interpreter.test.ts new file mode 100644 index 00000000000000..42443a2c32b82a --- /dev/null +++ b/src/plugins/expressions/public/interpreter.test.ts @@ -0,0 +1,48 @@ +/* + * 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 { getType } from './interpreter'; + +describe('getType()', () => { + test('returns "null" string for null or undefined', () => { + expect(getType(null)).toBe('null'); + expect(getType(undefined)).toBe('null'); + }); + + test('returns basic type name', () => { + expect(getType(0)).toBe('number'); + expect(getType(1)).toBe('number'); + expect(getType(0.8)).toBe('number'); + expect(getType(Infinity)).toBe('number'); + + expect(getType(true)).toBe('boolean'); + expect(getType(false)).toBe('boolean'); + }); + + test('returns .type property value of objects', () => { + expect(getType({ type: 'foo' })).toBe('foo'); + expect(getType({ type: 'bar' })).toBe('bar'); + }); + + test('throws if object has no .type property', () => { + expect(() => getType({})).toThrow(); + expect(() => getType({ _type: 'foo' })).toThrow(); + expect(() => getType({ tipe: 'foo' })).toThrow(); + }); +}); diff --git a/src/plugins/expressions/public/interpreter.ts b/src/plugins/expressions/public/interpreter.ts new file mode 100644 index 00000000000000..13bf245f233cbc --- /dev/null +++ b/src/plugins/expressions/public/interpreter.ts @@ -0,0 +1,91 @@ +/* + * 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 { get } from 'lodash'; +import { AnyExpressionType } from './types'; +import { ExpressionValue } from './types/types'; + +export function getType(node: any) { + if (node == null) return 'null'; + if (typeof node === 'object') { + if (!node.type) throw new Error('Objects must have a type property'); + return node.type; + } + return typeof node; +} + +export class Type { + name: string; + + /** + * A short help text. + */ + help: string; + + /** + * Type validation, useful for checking function output. + */ + validate: (type: any) => void | Error; + + create: unknown; + + /** + * Optional serialization (used when passing context around client/server). + */ + serialize?: (value: ExpressionValue) => any; + deserialize?: (serialized: any) => ExpressionValue; + + constructor(private readonly config: AnyExpressionType) { + const { name, help, deserialize, serialize, validate } = config; + + this.name = name; + this.help = help || ''; + this.validate = validate || (() => {}); + + // Optional + this.create = (config as any).create; + + this.serialize = serialize; + this.deserialize = deserialize; + } + + getToFn = (value: any) => get(this.config, ['to', value]) || get(this.config, ['to', '*']); + getFromFn = (value: any) => get(this.config, ['from', value]) || get(this.config, ['from', '*']); + + castsTo = (value: any) => typeof this.getToFn(value) === 'function'; + castsFrom = (value: any) => typeof this.getFromFn(value) === 'function'; + + to = (node: any, toTypeName: any, types: any) => { + const typeName = getType(node); + if (typeName !== this.name) { + throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`); + } else if (!this.castsTo(toTypeName)) { + throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`); + } + + return (this.getToFn(toTypeName) as any)(node, types); + }; + + from = (node: any, types: any) => { + const typeName = getType(node); + if (!this.castsFrom(typeName)) throw new Error(`Can not cast '${this.name}' from ${typeName}`); + + return (this.getFromFn(typeName) as any)(node, types); + }; +} diff --git a/src/plugins/expressions/common/expressions/interpreter_provider.ts b/src/plugins/expressions/public/interpreter_provider.ts similarity index 88% rename from src/plugins/expressions/common/expressions/interpreter_provider.ts rename to src/plugins/expressions/public/interpreter_provider.ts index 59ba1085a9d7da..cb84370ad69c52 100644 --- a/src/plugins/expressions/common/expressions/interpreter_provider.ts +++ b/src/plugins/expressions/public/interpreter_provider.ts @@ -21,11 +21,11 @@ import { clone, each, keys, last, mapValues, reduce, zipObject } from 'lodash'; // @ts-ignore -import { fromExpression, getByAlias, castProvider } from '@kbn/interpreter/common'; +import { fromExpression, getByAlias } from '@kbn/interpreter/common'; import { createError } from './create_error'; import { ExpressionAST, ExpressionFunctionAST, AnyExpressionFunction, ArgumentType } from './types'; -import { getType } from '../../common'; +import { getType } from './interpreter'; export { createError }; @@ -40,7 +40,30 @@ export type ExpressionInterpret = (ast: ExpressionAST, context?: any) => any; export function interpreterProvider(config: InterpreterConfig): ExpressionInterpret { const { functions, types } = config; const handlers = { ...config.handlers, types }; - const cast = castProvider(types); + + function cast(node: any, toTypeNames: any) { + // If you don't give us anything to cast to, you'll get your input back + if (!toTypeNames || toTypeNames.length === 0) return node; + + // No need to cast if node is already one of the valid types + const fromTypeName = getType(node); + if (toTypeNames.includes(fromTypeName)) return node; + + const fromTypeDef = types[fromTypeName]; + + for (let i = 0; i < toTypeNames.length; i++) { + // First check if the current type can cast to this type + if (fromTypeDef && fromTypeDef.castsTo(toTypeNames[i])) { + return fromTypeDef.to(node, toTypeNames[i], types); + } + + // If that isn't possible, check if this type can cast from the current type + const toTypeDef = types[toTypeNames[i]]; + if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) return toTypeDef.from(node, types); + } + + throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); + } async function invokeChain(chainArr: ExpressionFunctionAST[], context: any): Promise { if (!chainArr.length) return context; diff --git a/src/plugins/expressions/public/mocks.ts b/src/plugins/expressions/public/mocks.ts index 56ff343e70ac4f..9b47aa2b0cd978 100644 --- a/src/plugins/expressions/public/mocks.ts +++ b/src/plugins/expressions/public/mocks.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { Plugin } from '.'; +import { ExpressionsSetup, ExpressionsStart } from '.'; -export type Setup = jest.Mocked>; -export type Start = jest.Mocked>; +export type Setup = jest.Mocked; +export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { @@ -36,28 +36,18 @@ const createSetupContract = (): Setup => { types: { register: () => {}, } as any, + getExecutor: () => ({ + interpreter: { + interpretAst: () => {}, + }, + }), }, }; return setupContract; }; const createStartContract = (): Start => { - const startContract: Start = { - registerFunction: jest.fn(), - registerRenderer: jest.fn(), - registerType: jest.fn(), - __LEGACY: { - functions: { - register: () => {}, - } as any, - renderers: { - register: () => {}, - } as any, - types: { - register: () => {}, - } as any, - }, - }; + const startContract: Start = undefined; return startContract; }; diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index b301981f58801d..e67b503f301aab 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -19,29 +19,133 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { - ExpressionsService, - ExpressionsSetupContract, - ExpressionsStartContract, -} from './expressions/expressions_service'; + AnyExpressionFunction, + AnyExpressionType, + ExpressionInterpretWithHandlers, + ExpressionExecutor, +} from './types'; +import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './registries'; +import { Setup as InspectorSetup, Start as InspectorStart } from '../../inspector/public'; +import { setCoreStart } from './services'; +import { clog as clogFunction } from './functions/clog'; +import { font as fontFunction } from './functions/font'; +import { kibana as kibanaFunction } from './functions/kibana'; +import { kibanaContext as kibanaContextFunction } from './functions/kibana_context'; +import { + boolean as booleanType, + datatable as datatableType, + error as errorType, + filter as filterType, + image as imageType, + nullType, + number as numberType, + pointseries, + range as rangeType, + render as renderType, + shape as shapeType, + string as stringType, + style as styleType, + kibanaContext as kibanaContextType, + kibanaDatatable as kibanaDatatableType, +} from './expression_types'; +import { interpreterProvider } from './interpreter_provider'; +import { createHandlers } from './create_handlers'; + +export interface ExpressionsSetupDeps { + inspector: InspectorSetup; +} + +export interface ExpressionsStartDeps { + inspector: InspectorStart; +} + +export interface ExpressionsSetup { + registerFunction: (fn: AnyExpressionFunction | (() => AnyExpressionFunction)) => void; + registerRenderer: (renderer: any) => void; + registerType: (type: () => AnyExpressionType) => void; + __LEGACY: { + functions: FunctionsRegistry; + renderers: RenderFunctionsRegistry; + types: TypesRegistry; + getExecutor: () => ExpressionExecutor; + }; +} + +export type ExpressionsStart = void; export class ExpressionsPublicPlugin - implements Plugin<{}, {}, ExpressionsSetupContract, ExpressionsStartContract> { - private readonly expressions = new ExpressionsService(); + implements + Plugin { + private readonly functions = new FunctionsRegistry(); + private readonly renderers = new RenderFunctionsRegistry(); + private readonly types = new TypesRegistry(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup): ExpressionsSetupContract { - const expressions = this.expressions.setup(); - return { - ...expressions, + public setup(core: CoreSetup, { inspector }: ExpressionsSetupDeps): ExpressionsSetup { + const { functions, renderers, types } = this; + + const registerFunction: ExpressionsSetup['registerFunction'] = fn => { + functions.register(fn); + }; + + registerFunction(clogFunction); + registerFunction(fontFunction); + registerFunction(kibanaFunction); + registerFunction(kibanaContextFunction); + + types.register(booleanType); + types.register(datatableType); + types.register(errorType); + types.register(filterType); + types.register(imageType); + types.register(nullType); + types.register(numberType); + types.register(pointseries); + types.register(rangeType); + types.register(renderType); + types.register(shapeType); + types.register(stringType); + types.register(styleType); + types.register(kibanaContextType); + types.register(kibanaDatatableType); + + // TODO: Refactor this function. + const getExecutor = () => { + const interpretAst: ExpressionInterpretWithHandlers = (ast, context, handlers) => { + const interpret = interpreterProvider({ + types: types.toJS(), + handlers: { ...handlers, ...createHandlers() }, + functions: functions.toJS(), + }); + return interpret(ast, context); + }; + const executor: ExpressionExecutor = { interpreter: { interpretAst } }; + return executor; }; - } - public start(core: CoreStart): ExpressionsStartContract { - const expressions = this.expressions.start(); - return { - ...expressions, + const setup: ExpressionsSetup = { + registerFunction, + registerRenderer: (renderer: any) => { + renderers.register(renderer); + }, + registerType: type => { + types.register(type); + }, + __LEGACY: { + functions, + renderers, + types, + getExecutor, + }, }; + + return setup; + } + + public start(core: CoreStart, { inspector }: ExpressionsStartDeps): ExpressionsStart { + setCoreStart(core); } + public stop() {} } diff --git a/src/plugins/expressions/public/registries/function_registry.ts b/src/plugins/expressions/public/registries/function_registry.ts new file mode 100644 index 00000000000000..c15b7ceb0d2178 --- /dev/null +++ b/src/plugins/expressions/public/registries/function_registry.ts @@ -0,0 +1,127 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +import { AnyExpressionFunction, FunctionHandlers } from '../types/functions'; +import { ExpressionValue } from '../types/types'; +import { ArgumentType } from '../types'; +import { Registry } from './registry'; + +export class FunctionParameter { + name: string; + required: boolean; + help: string; + types: string[]; + default: any; + aliases: string[]; + multi: boolean; + resolve: boolean; + options: any[]; + + constructor(name: string, arg: ArgumentType) { + const { required, help, types, aliases, multi, resolve, options } = arg; + + if (name === '_') { + throw Error('Arg names must not be _. Use it in aliases instead.'); + } + + this.name = name; + this.required = !!required; + this.help = help || ''; + this.types = types || []; + this.default = arg.default; + this.aliases = aliases || []; + this.multi = !!multi; + this.resolve = resolve == null ? true : resolve; + this.options = options || []; + } + + accepts(type: string) { + if (!this.types.length) return true; + return this.types.indexOf(type) > -1; + } +} + +export class Function { + /** + * Name of function + */ + name: string; + + /** + * Aliases that can be used instead of `name`. + */ + aliases: string[]; + + /** + * Return type of function. This SHOULD be supplied. We use it for UI + * and autocomplete hinting. We may also use it for optimizations in + * the future. + */ + type: string; + + /** + * Function to run function (context, args) + */ + fn: ( + input: ExpressionValue, + params: Record, + handlers: FunctionHandlers + ) => ExpressionValue; + + /** + * A short help text. + */ + help: string; + + args: Record = {}; + + context: { types?: string[] }; + + constructor(functionDefinition: AnyExpressionFunction) { + const { name, type, aliases, fn, help, args, context } = functionDefinition; + + this.name = name; + this.type = type; + this.aliases = aliases || []; + this.fn = (input, params, handlers) => Promise.resolve(fn(input, params, handlers)); + this.help = help || ''; + this.context = context || {}; + + for (const [key, arg] of Object.entries(args || {})) { + this.args[key] = new FunctionParameter(key, arg); + } + } + + accepts = (type: string): boolean => { + // If you don't tell us about context, we'll assume you don't care what you get. + if (!this.context.types) return true; + return this.context.types.indexOf(type) > -1; + }; +} + +export class FunctionsRegistry extends Registry { + register(functionDefinition: AnyExpressionFunction | (() => AnyExpressionFunction)) { + const fn = new Function( + typeof functionDefinition === 'object' ? functionDefinition : functionDefinition() + ); + this.set(fn.name, fn); + } +} diff --git a/src/plugins/expressions/public/registries/index.ts b/src/plugins/expressions/public/registries/index.ts new file mode 100644 index 00000000000000..16c8d8fc4c93a5 --- /dev/null +++ b/src/plugins/expressions/public/registries/index.ts @@ -0,0 +1,22 @@ +/* + * 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 './type_registry'; +export * from './function_registry'; +export * from './render_registry'; diff --git a/src/plugins/expressions/public/registries/registry.ts b/src/plugins/expressions/public/registries/registry.ts new file mode 100644 index 00000000000000..fe149116fbf147 --- /dev/null +++ b/src/plugins/expressions/public/registries/registry.ts @@ -0,0 +1,42 @@ +/* + * 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 class Registry { + private data: Record = {}; + + set(id: string, item: T) { + this.data[id] = item; + } + + get(id: string): T | null { + return this.data[id] || null; + } + + toJS(): Record { + return { ...this.data }; + } + + toArray(): T[] { + return Object.values(this.data); + } + + reset() { + this.data = {}; + } +} diff --git a/src/plugins/expressions/public/registries/render_registry.ts b/src/plugins/expressions/public/registries/render_registry.ts new file mode 100644 index 00000000000000..6fd48f5f0c6af9 --- /dev/null +++ b/src/plugins/expressions/public/registries/render_registry.ts @@ -0,0 +1,83 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +import { Registry } from './registry'; + +export interface ExpressionRenderDefinition { + name: string; + displayName: string; + help?: string; + validate?: () => void | Error; + reuseDomNode: boolean; + render: (domNode: HTMLElement, config: Config, handlers: any) => Promise; +} + +class ExpressionRenderFunction { + /** + * This must match the name of the function that is used to create the `type: render` object. + */ + name: string; + + /** + * Use this to set a more friendly name. + */ + displayName: string; + + /** + * A sentence or few about what this element does. + */ + help: string; + + /** + * Used to validate the data before calling the render function. + */ + validate: () => void | Error; + + /** + * Tell the renderer if the dom node should be reused, it's recreated each time by default. + */ + reuseDomNode: boolean; + + /** + * The function called to render the data. + */ + render: (domNode: HTMLElement, config: any, handlers: any) => Promise; + + constructor(config: ExpressionRenderDefinition) { + const { name, displayName, help, validate, reuseDomNode, render } = config; + + this.name = name; + this.displayName = displayName || name; + this.help = help || ''; + this.validate = validate || (() => {}); + this.reuseDomNode = Boolean(reuseDomNode); + this.render = render; + } +} + +export class RenderFunctionsRegistry extends Registry { + register(definition: ExpressionRenderDefinition | (() => ExpressionRenderDefinition)) { + const renderFunction = new ExpressionRenderFunction( + typeof definition === 'object' ? definition : definition() + ); + this.set(renderFunction.name, renderFunction); + } +} diff --git a/src/plugins/expressions/public/registries/type_registry.ts b/src/plugins/expressions/public/registries/type_registry.ts new file mode 100644 index 00000000000000..bcc801213582eb --- /dev/null +++ b/src/plugins/expressions/public/registries/type_registry.ts @@ -0,0 +1,29 @@ +/* + * 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 { Registry } from './registry'; +import { Type } from '../interpreter'; +import { AnyExpressionType } from '../types'; + +export class TypesRegistry extends Registry { + register(typeDefinition: AnyExpressionType | (() => AnyExpressionType)) { + const type = new Type(typeof typeDefinition === 'object' ? typeDefinition : typeDefinition()); + this.set(type.name, type); + } +} diff --git a/src/plugins/expressions/common/expressions/serialize_provider.ts b/src/plugins/expressions/public/serialize_provider.ts similarity index 84% rename from src/plugins/expressions/common/expressions/serialize_provider.ts rename to src/plugins/expressions/public/serialize_provider.ts index 1bd06a38a25602..449d47c183dbb9 100644 --- a/src/plugins/expressions/common/expressions/serialize_provider.ts +++ b/src/plugins/expressions/public/serialize_provider.ts @@ -18,15 +18,7 @@ */ import { get, identity } from 'lodash'; - -export function getType(node: any) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; - } - return typeof node; -} +import { getType } from './interpreter'; export function serializeProvider(types: any) { return { diff --git a/src/plugins/expressions/public/services.ts b/src/plugins/expressions/public/services.ts new file mode 100644 index 00000000000000..a1a42aa85e670f --- /dev/null +++ b/src/plugins/expressions/public/services.ts @@ -0,0 +1,35 @@ +/* + * 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 { createKibanaUtilsCore, createGetterSetter } from '../../kibana_utils/public'; +import { ExpressionInterpreter } from './types'; +import { Start as IInspector } from '../../inspector/public'; +import { ExpressionsSetup } from './plugin'; + +export const { getCoreStart, setCoreStart, savedObjects } = createKibanaUtilsCore(); + +export const [getInspector, setInspector] = createGetterSetter('Inspector'); + +export const [getInterpreter, setInterpreter] = createGetterSetter( + 'Interpreter' +); + +export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< + ExpressionsSetup['__LEGACY']['renderers'] +>('Renderers registry'); diff --git a/src/plugins/expressions/common/expressions/types/arguments.ts b/src/plugins/expressions/public/types/arguments.ts similarity index 100% rename from src/plugins/expressions/common/expressions/types/arguments.ts rename to src/plugins/expressions/public/types/arguments.ts diff --git a/src/plugins/expressions/common/expressions/types/common.ts b/src/plugins/expressions/public/types/common.ts similarity index 100% rename from src/plugins/expressions/common/expressions/types/common.ts rename to src/plugins/expressions/public/types/common.ts diff --git a/src/plugins/expressions/common/expressions/types/functions.ts b/src/plugins/expressions/public/types/functions.ts similarity index 100% rename from src/plugins/expressions/common/expressions/types/functions.ts rename to src/plugins/expressions/public/types/functions.ts diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts new file mode 100644 index 00000000000000..2d66216a9770ba --- /dev/null +++ b/src/plugins/expressions/public/types/index.ts @@ -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 { ExpressionInterpret } from '../interpreter_provider'; +import { TimeRange } from '../../../data/public'; +import { Adapters } from '../../../inspector/public'; +import { Query } from '../../../data/public'; +import { ExpressionAST } from '../../../expressions/public'; +import { ExpressionArgAST } from '../../../../plugins/expressions/public'; +import { esFilters } from '../../../../plugins/data/public'; + +export { ArgumentType } from './arguments'; +export { + TypeToString, + KnownTypeToString, + TypeString, + UnmappedTypeStrings, + UnwrapPromise, + SerializedFieldFormat, +} from './common'; + +export { ExpressionFunction, AnyExpressionFunction, FunctionHandlers } from './functions'; +export { ExpressionType, AnyExpressionType } from './types'; + +export * from './style'; + +export type ExpressionArgAST = string | boolean | number | ExpressionAST; + +export interface ExpressionFunctionAST { + type: 'function'; + function: string; + arguments: { + [key: string]: ExpressionArgAST[]; + }; +} + +export interface ExpressionAST { + type: 'expression'; + chain: ExpressionFunctionAST[]; +} + +export type ExpressionInterpretWithHandlers = ( + ast: Parameters[0], + context: Parameters[1], + handlers: IInterpreterHandlers +) => ReturnType; + +export interface ExpressionInterpreter { + interpretAst: ExpressionInterpretWithHandlers; +} + +export interface ExpressionExecutor { + interpreter: ExpressionInterpreter; +} + +export type RenderId = number; +export type Data = any; +export type event = any; +export type Context = object; + +export interface SearchContext { + type: 'kibana_context'; + filters?: esFilters.Filter[]; + query?: Query; + timeRange?: TimeRange; +} + +export type IGetInitialContext = () => SearchContext | Context; + +export interface IExpressionLoaderParams { + searchContext?: SearchContext; + context?: Context; + variables?: Record; + disableCaching?: boolean; + customFunctions?: []; + customRenderers?: []; + extraHandlers?: Record; +} + +export interface IInterpreterHandlers { + getInitialContext: IGetInitialContext; + inspectorAdapters?: Adapters; + abortSignal?: AbortSignal; +} + +export interface IInterpreterRenderHandlers { + /** + * Done increments the number of rendering successes + */ + done: () => void; + onDestroy: (fn: () => void) => void; + reload: () => void; + update: (params: any) => void; + event: (event: event) => void; +} + +export interface IInterpreterRenderFunction { + name: string; + displayName: string; + help: string; + validate: () => void; + reuseDomNode: boolean; + render: (domNode: Element, data: T, handlers: IInterpreterRenderHandlers) => void | Promise; +} + +export interface IInterpreterErrorResult { + type: 'error'; + error: { message: string; name: string; stack: string }; +} + +export interface IInterpreterSuccessResult { + type: string; + as?: string; + value?: unknown; + error?: unknown; +} + +export type IInterpreterResult = IInterpreterSuccessResult & IInterpreterErrorResult; + +export interface IInterpreter { + interpretAst( + ast: ExpressionAST, + context: Context, + handlers: IInterpreterHandlers + ): Promise; +} diff --git a/src/plugins/expressions/public/types/style.ts b/src/plugins/expressions/public/types/style.ts new file mode 100644 index 00000000000000..9f919223d558c4 --- /dev/null +++ b/src/plugins/expressions/public/types/style.ts @@ -0,0 +1,137 @@ +/* + * 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 { FontLabel } from '../fonts'; + +/** + * Enum of supported CSS `background-repeat` properties. + */ +export enum BackgroundRepeat { + REPEAT = 'repeat', + REPEAT_NO = 'no-repeat', + REPEAT_X = 'repeat-x', + REPEAT_Y = 'repeat-y', + ROUND = 'round', + SPACE = 'space', +} + +/** + * Enum of supported CSS `background-size` properties. + */ +export enum BackgroundSize { + AUTO = 'auto', + CONTAIN = 'contain', + COVER = 'cover', +} + +/** + * Enum of supported CSS `font-style` properties. + */ +export enum FontStyle { + ITALIC = 'italic', + NORMAL = 'normal', +} + +/** + * Enum of supported CSS `font-weight` properties. + */ +export enum FontWeight { + NORMAL = 'normal', + BOLD = 'bold', + BOLDER = 'bolder', + LIGHTER = 'lighter', + ONE = '100', + TWO = '200', + THREE = '300', + FOUR = '400', + FIVE = '500', + SIX = '600', + SEVEN = '700', + EIGHT = '800', + NINE = '900', +} + +/** + * Enum of supported CSS `overflow` properties. + */ +export enum Overflow { + AUTO = 'auto', + HIDDEN = 'hidden', + SCROLL = 'scroll', + VISIBLE = 'visible', +} + +/** + * Enum of supported CSS `text-align` properties. + */ +export enum TextAlignment { + CENTER = 'center', + JUSTIFY = 'justify', + LEFT = 'left', + RIGHT = 'right', +} + +/** + * Enum of supported CSS `text-decoration` properties. + */ +export enum TextDecoration { + NONE = 'none', + UNDERLINE = 'underline', +} + +/** + * Represents the various style properties that can be applied to an element. + */ +export interface CSSStyle { + color?: string; + fill?: string; + fontFamily?: FontLabel; + fontSize?: string; + fontStyle?: FontStyle; + fontWeight?: FontWeight; + lineHeight?: number | string; + textAlign?: TextAlignment; + textDecoration?: TextDecoration; +} + +/** + * Represents an object containing style information for a Container. + */ +export interface ContainerStyle { + border: string | null; + borderRadius: string | null; + padding: string | null; + backgroundColor: string | null; + backgroundImage: string | null; + backgroundSize: BackgroundSize; + backgroundRepeat: BackgroundRepeat; + opacity: number | null; + overflow: Overflow; +} + +/** + * An object that represents style information, typically CSS. + */ +export interface ExpressionTypeStyle { + type: 'style'; + spec: CSSStyle; + css: string; +} + +export type Style = ExpressionTypeStyle; diff --git a/src/plugins/expressions/public/types/types.ts b/src/plugins/expressions/public/types/types.ts new file mode 100644 index 00000000000000..e7b30d24fa6eb5 --- /dev/null +++ b/src/plugins/expressions/public/types/types.ts @@ -0,0 +1,57 @@ +/* + * 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 type ExpressionValueUnboxed = any; + +export type ExpressionValueBoxed = { + type: Type; +} & Value; + +export type ExpressionValue = ExpressionValueUnboxed | ExpressionValueBoxed; + +export type ExpressionValueConverter = ( + input: I, + availableTypes: Record +) => O; + +/** + * A generic type which represents a custom Expression Type Definition that's + * registered to the Interpreter. + */ +export interface ExpressionType< + Name extends string, + Value extends ExpressionValueUnboxed | ExpressionValueBoxed, + SerializedType = undefined +> { + name: Name; + validate?: (type: any) => void | Error; + serialize?: (type: Value) => SerializedType; + deserialize?: (type: SerializedType) => Value; + // TODO: Update typings for the `availableTypes` parameter once interfaces for this + // have been added elsewhere in the interpreter. + from?: { + [type: string]: ExpressionValueConverter; + }; + to?: { + [type: string]: ExpressionValueConverter; + }; + help?: string; +} + +export type AnyExpressionType = ExpressionType; diff --git a/src/plugins/feature_catalogue/README.md b/src/plugins/feature_catalogue/README.md new file mode 100644 index 00000000000000..68584e7ed2ce13 --- /dev/null +++ b/src/plugins/feature_catalogue/README.md @@ -0,0 +1,26 @@ +# Feature catalogue plugin + +Replaces the legacy `ui/registry/feature_catalogue` module for registering "features" that should be showed in the home +page's feature catalogue. This should not be confused with the "feature" plugin for registering features used to derive +UI capabilities for feature controls. + +## Example registration + +```ts +// For legacy plugins +import { npSetup } from 'ui/new_platform'; +npSetup.plugins.feature_catalogue.register(/* same details here */); + +// For new plugins: first add 'feature_catalogue` to the list of `optionalPlugins` +// in your kibana.json file. Then access the plugin directly in `setup`: + +class MyPlugin { + setup(core, plugins) { + if (plugins.feature_catalogue) { + plugins.feature_catalogue.register(/* same details here. */); + } + } +} +``` + +Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0. diff --git a/src/plugins/feature_catalogue/kibana.json b/src/plugins/feature_catalogue/kibana.json new file mode 100644 index 00000000000000..3f39c9361f0476 --- /dev/null +++ b/src/plugins/feature_catalogue/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "feature_catalogue", + "version": "kibana", + "server": false, + "ui": true +} diff --git a/src/plugins/feature_catalogue/public/index.ts b/src/plugins/feature_catalogue/public/index.ts new file mode 100644 index 00000000000000..dd241a317c4a6a --- /dev/null +++ b/src/plugins/feature_catalogue/public/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { FeatureCatalogueSetup, FeatureCatalogueStart } from './plugin'; +export { FeatureCatalogueEntry, FeatureCatalogueCategory } from './services'; +import { FeatureCataloguePlugin } from './plugin'; + +export const plugin = () => new FeatureCataloguePlugin(); diff --git a/src/plugins/feature_catalogue/public/plugin.test.mocks.ts b/src/plugins/feature_catalogue/public/plugin.test.mocks.ts new file mode 100644 index 00000000000000..c0da6a179204b6 --- /dev/null +++ b/src/plugins/feature_catalogue/public/plugin.test.mocks.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. + */ + +import { featureCatalogueRegistryMock } from './services/feature_catalogue_registry.mock'; + +export const registryMock = featureCatalogueRegistryMock.create(); +jest.doMock('./services', () => ({ + FeatureCatalogueRegistry: jest.fn(() => registryMock), +})); diff --git a/src/plugins/feature_catalogue/public/plugin.test.ts b/src/plugins/feature_catalogue/public/plugin.test.ts new file mode 100644 index 00000000000000..8bbbb973b459ee --- /dev/null +++ b/src/plugins/feature_catalogue/public/plugin.test.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 { registryMock } from './plugin.test.mocks'; +import { FeatureCataloguePlugin } from './plugin'; + +describe('FeatureCataloguePlugin', () => { + beforeEach(() => { + registryMock.setup.mockClear(); + registryMock.start.mockClear(); + }); + + describe('setup', () => { + test('wires up and returns registry', async () => { + const setup = await new FeatureCataloguePlugin().setup(); + expect(registryMock.setup).toHaveBeenCalledWith(); + expect(setup.register).toBeDefined(); + }); + }); + + describe('start', () => { + test('wires up and returns registry', async () => { + const service = new FeatureCataloguePlugin(); + await service.setup(); + const core = { application: { capabilities: { catalogue: {} } } } as any; + const start = await service.start(core); + expect(registryMock.start).toHaveBeenCalledWith({ + capabilities: core.application.capabilities, + }); + expect(start.get).toBeDefined(); + }); + }); +}); diff --git a/src/plugins/feature_catalogue/public/plugin.ts b/src/plugins/feature_catalogue/public/plugin.ts new file mode 100644 index 00000000000000..46a70baff488af --- /dev/null +++ b/src/plugins/feature_catalogue/public/plugin.ts @@ -0,0 +1,50 @@ +/* + * 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 { CoreStart, Plugin } from 'src/core/public'; +import { + FeatureCatalogueRegistry, + FeatureCatalogueRegistrySetup, + FeatureCatalogueRegistryStart, +} from './services'; + +export class FeatureCataloguePlugin + implements Plugin { + private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); + + public async setup() { + return { + ...this.featuresCatalogueRegistry.setup(), + }; + } + + public async start(core: CoreStart) { + return { + ...this.featuresCatalogueRegistry.start({ + capabilities: core.application.capabilities, + }), + }; + } +} + +/** @public */ +export type FeatureCatalogueSetup = FeatureCatalogueRegistrySetup; + +/** @public */ +export type FeatureCatalogueStart = FeatureCatalogueRegistryStart; diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts new file mode 100644 index 00000000000000..54bdd42c1cca97 --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.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 { + FeatureCatalogueRegistrySetup, + FeatureCatalogueRegistryStart, + FeatureCatalogueRegistry, +} from './feature_catalogue_registry'; + +const createSetupMock = (): jest.Mocked => { + const setup = { + register: jest.fn(), + }; + return setup; +}; + +const createStartMock = (): jest.Mocked => { + const start = { + get: jest.fn(), + }; + return start; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + start: jest.fn(), + }; + service.setup.mockImplementation(createSetupMock); + service.start.mockImplementation(createStartMock); + return service; +}; + +export const featureCatalogueRegistryMock = { + createSetup: createSetupMock, + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts new file mode 100644 index 00000000000000..b174a68aa53be2 --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { + FeatureCatalogueRegistry, + FeatureCatalogueCategory, + FeatureCatalogueEntry, +} from './feature_catalogue_registry'; + +const DASHBOARD_FEATURE: FeatureCatalogueEntry = { + id: 'dashboard', + title: 'Dashboard', + description: 'Display and share a collection of visualizations and saved searches.', + icon: 'dashboardApp', + path: `/app/kibana#dashboard`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, +}; + +describe('FeatureCatalogueRegistry', () => { + describe('setup', () => { + test('throws when registering duplicate id', () => { + const setup = new FeatureCatalogueRegistry().setup(); + setup.register(DASHBOARD_FEATURE); + expect(() => setup.register(DASHBOARD_FEATURE)).toThrowErrorMatchingInlineSnapshot( + `"Feature with id [dashboard] has already been registered. Use a unique id."` + ); + }); + }); + + describe('start', () => { + describe('capabilities filtering', () => { + test('retains items with no entry in capabilities', () => { + const service = new FeatureCatalogueRegistry(); + service.setup().register(DASHBOARD_FEATURE); + const capabilities = { catalogue: {} } as any; + expect(service.start({ capabilities }).get()).toEqual([DASHBOARD_FEATURE]); + }); + + test('retains items with true in capabilities', () => { + const service = new FeatureCatalogueRegistry(); + service.setup().register(DASHBOARD_FEATURE); + const capabilities = { catalogue: { dashboard: true } } as any; + expect(service.start({ capabilities }).get()).toEqual([DASHBOARD_FEATURE]); + }); + + test('removes items with false in capabilities', () => { + const service = new FeatureCatalogueRegistry(); + service.setup().register(DASHBOARD_FEATURE); + const capabilities = { catalogue: { dashboard: false } } as any; + expect(service.start({ capabilities }).get()).toEqual([]); + }); + }); + }); + + describe('title sorting', () => { + test('sorts by title ascending', () => { + const service = new FeatureCatalogueRegistry(); + const setup = service.setup(); + setup.register({ id: '1', title: 'Orange' } as any); + setup.register({ id: '2', title: 'Apple' } as any); + setup.register({ id: '3', title: 'Banana' } as any); + const capabilities = { catalogue: {} } as any; + expect(service.start({ capabilities }).get()).toEqual([ + { id: '2', title: 'Apple' }, + { id: '3', title: 'Banana' }, + { id: '1', title: 'Orange' }, + ]); + }); + }); +}); diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts new file mode 100644 index 00000000000000..6ab342f37dfd9f --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts @@ -0,0 +1,86 @@ +/* + * 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 { Capabilities } from 'src/core/public'; +import { IconType } from '@elastic/eui'; + +/** @public */ +export enum FeatureCatalogueCategory { + ADMIN = 'admin', + DATA = 'data', + OTHER = 'other', +} + +/** @public */ +export interface FeatureCatalogueEntry { + /** Unique string identifier for this feature. */ + readonly id: string; + /** Title of feature displayed to the user. */ + readonly title: string; + /** {@link FeatureCatalogueCategory} to display this feature in. */ + readonly category: FeatureCatalogueCategory; + /** One-line description of feature displayed to the user. */ + readonly description: string; + /** EUI `IconType` for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or ReactElement. */ + readonly icon: IconType; + /** URL path to link to this future. Should not include the basePath. */ + readonly path: string; + /** Whether or not this link should be shown on the front page of Kibana. */ + readonly showOnHomePage: boolean; +} + +export class FeatureCatalogueRegistry { + private readonly features = new Map(); + + public setup() { + return { + register: (feature: FeatureCatalogueEntry) => { + if (this.features.has(feature.id)) { + throw new Error( + `Feature with id [${feature.id}] has already been registered. Use a unique id.` + ); + } + + this.features.set(feature.id, feature); + }, + }; + } + + public start({ capabilities }: { capabilities: Capabilities }) { + return { + get: (): readonly FeatureCatalogueEntry[] => + [...this.features.values()] + .filter(entry => capabilities.catalogue[entry.id] !== false) + .sort(compareByKey('title')), + }; + } +} + +export type FeatureCatalogueRegistrySetup = ReturnType; +export type FeatureCatalogueRegistryStart = ReturnType; + +const compareByKey = (key: keyof T) => (left: T, right: T) => { + if (left[key] < right[key]) { + return -1; + } else if (left[key] > right[key]) { + return 1; + } else { + return 0; + } +}; diff --git a/src/plugins/feature_catalogue/public/services/index.ts b/src/plugins/feature_catalogue/public/services/index.ts new file mode 100644 index 00000000000000..17433264f5a428 --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/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 './feature_catalogue_registry'; diff --git a/src/plugins/kibana_react/public/context/context.test.tsx b/src/plugins/kibana_react/public/context/context.test.tsx index d7dce3f69239dd..4008daf69f25d4 100644 --- a/src/plugins/kibana_react/public/context/context.test.tsx +++ b/src/plugins/kibana_react/public/context/context.test.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { context, createKibanaReactContext, useKibana, KibanaContextProvider } from './context'; import { coreMock, overlayServiceMock } from '../../../../core/public/mocks'; -import { CoreStart } from './types'; +import { CoreStart } from '../../../../core/public'; let container: HTMLDivElement | null; diff --git a/src/plugins/kibana_react/public/context/context.tsx b/src/plugins/kibana_react/public/context/context.tsx index 630348017106ab..cbae5c4638ca2d 100644 --- a/src/plugins/kibana_react/public/context/context.tsx +++ b/src/plugins/kibana_react/public/context/context.tsx @@ -42,11 +42,11 @@ export const useKibana = (): KibanaReactContextValue< export const withKibana = }>( type: React.ComponentType ): React.FC> => { - const enhancedType: React.FC> = (props: Omit) => { + const EnhancedType: React.FC> = (props: Omit) => { const kibana = useKibana(); return React.createElement(type, { ...props, kibana } as Props); }; - return enhancedType; + return EnhancedType; }; export const UseKibana: React.FC<{ @@ -69,7 +69,7 @@ export const createKibanaReactContext = ( const oldValue = useKibana(); const { value: newValue } = useMemo( () => createKibanaReactContext({ ...services, ...oldValue.services, ...newServices }), - Object.keys(services) + [services, oldValue, newServices] ); return createElement(context.Provider as React.ComponentType, { value: newValue, diff --git a/src/plugins/kibana_react/public/context/index.ts b/src/plugins/kibana_react/public/context/index.ts index 444ef343cd7d29..c7c0693a89f2b6 100644 --- a/src/plugins/kibana_react/public/context/index.ts +++ b/src/plugins/kibana_react/public/context/index.ts @@ -25,4 +25,4 @@ export { withKibana, UseKibana, } from './context'; -export { KibanaReactContext, KibanaReactContextValue } from './types'; +export { KibanaReactContext, KibanaReactContextValue, KibanaServices } from './types'; diff --git a/src/plugins/kibana_react/public/context/types.ts b/src/plugins/kibana_react/public/context/types.ts index 1906bb808ea87b..35e6349bfca87c 100644 --- a/src/plugins/kibana_react/public/context/types.ts +++ b/src/plugins/kibana_react/public/context/types.ts @@ -22,8 +22,6 @@ import { CoreStart } from '../../../../core/public'; import { KibanaReactOverlays } from '../overlays'; import { KibanaReactNotifications } from '../notifications'; -export { CoreStart }; - export type KibanaServices = Partial; export interface KibanaReactContextValue { diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index 81d514ca4d466d..a880d3c6cf87c3 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -24,11 +24,11 @@ import { EuiScreenReaderOnly, keyCodes } from '@elastic/eui'; // @ts-ignore import { KuiButton } from '@kbn/ui-framework/components'; -interface Props { +export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; } -class ExitFullScreenButtonUi extends PureComponent { +class ExitFullScreenButtonUi extends PureComponent { public onKeyDown = (e: KeyboardEvent) => { if (e.keyCode === keyCodes.ESCAPE) { this.props.onExitFullScreenMode(); diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx index a965fd776e0c29..130ac1e980eee3 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/index.tsx @@ -17,4 +17,4 @@ * under the License. */ -export { ExitFullScreenButton } from './exit_full_screen_button'; +export { ExitFullScreenButton, ExitFullScreenButtonProps } from './exit_full_screen_button'; diff --git a/src/plugins/kibana_react/public/overlays/types.ts b/src/plugins/kibana_react/public/overlays/types.ts index 6a1fb25ca14835..0108d8eaba6cc9 100644 --- a/src/plugins/kibana_react/public/overlays/types.ts +++ b/src/plugins/kibana_react/public/overlays/types.ts @@ -18,7 +18,7 @@ */ import * as React from 'react'; -import { CoreStart } from '../context/types'; +import { CoreStart } from '../../../../core/public'; export interface KibanaReactOverlays { openFlyout: ( diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx index fbea5d613f60ca..c65d4289587670 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx @@ -104,7 +104,7 @@ interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { initialPageSize?: 5 | 10 | 15 | 25; fixedPageSize?: undefined; } -type SavedObjectFinderProps = { +export type SavedObjectFinderProps = { savedObjects: CoreStart['savedObjects']; uiSettings: CoreStart['uiSettings']; } & (SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize); diff --git a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx index cc2a8c3fbe1fc6..0879b0cb3f36a1 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx @@ -106,7 +106,7 @@ describe('useUiSetting', () => { }); describe('useUiSetting$', () => { - const TestConsumer$: React.FC<{ + const TestConsumerX: React.FC<{ setting: string; newValue?: string; }> = ({ setting, newValue = '' }) => { @@ -126,7 +126,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -143,7 +143,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -159,7 +159,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -174,7 +174,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); diff --git a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts index ddc8b2a6847289..295515bfa51af6 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts @@ -65,6 +65,7 @@ export const useUiSetting$ = (key: string, defaultValue?: T): [T, Setter] const observable$ = useMemo(() => services.uiSettings!.get$(key, defaultValue), [ key, defaultValue, + services.uiSettings, ]); const value = useObservable(observable$, services.uiSettings!.get(key, defaultValue)); const set = useCallback((newValue: T) => services.uiSettings!.set(key, newValue), [key]); diff --git a/src/plugins/kibana_utils/public/core/create_getter_setter.ts b/src/plugins/kibana_utils/public/core/create_getter_setter.ts new file mode 100644 index 00000000000000..be2fd48ee6e7b4 --- /dev/null +++ b/src/plugins/kibana_utils/public/core/create_getter_setter.ts @@ -0,0 +1,36 @@ +/* + * 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 type Get = () => T; +export type Set = (value: T) => void; + +export const createGetterSetter = (name: string): [Get, Set] => { + let value: T; + + const get: Get = () => { + if (!value) throw new Error(`${name} was not set.`); + return value; + }; + + const set: Set = newValue => { + value = newValue; + }; + + return [get, set]; +}; diff --git a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts new file mode 100644 index 00000000000000..c5b23bdf0055f2 --- /dev/null +++ b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.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 { createKibanaUtilsCore } from './create_kibana_utils_core'; +import { CoreStart } from 'kibana/public'; + +describe('createKibanaUtilsCore', () => { + it('should allows to work with multiple instances', () => { + const core1 = {} as CoreStart; + const core2 = {} as CoreStart; + + const { setCoreStart: setCoreStart1, getCoreStart: getCoreStart1 } = createKibanaUtilsCore(); + const { setCoreStart: setCoreStart2, getCoreStart: getCoreStart2 } = createKibanaUtilsCore(); + + setCoreStart1(core1); + setCoreStart2(core2); + + expect(getCoreStart1()).toBe(core1); + expect(getCoreStart2()).toBe(core2); + + expect(getCoreStart1() !== getCoreStart2()).toBeTruthy(); + }); +}); diff --git a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts new file mode 100644 index 00000000000000..84ecffa1da6342 --- /dev/null +++ b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.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 { createGetterSetter, Get, Set } from './create_getter_setter'; +import { CoreStart } from '../../../../core/public'; +import { KUSavedObjectClient, createSavedObjectsClient } from './saved_objects_client'; + +interface Return { + getCoreStart: Get; + setCoreStart: Set; + savedObjects: KUSavedObjectClient; +} + +export const createKibanaUtilsCore = (): Return => { + const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); + const savedObjects = createSavedObjectsClient(getCoreStart); + + return { + getCoreStart, + setCoreStart, + savedObjects, + }; +}; diff --git a/src/plugins/kibana_utils/public/core/index.ts b/src/plugins/kibana_utils/public/core/index.ts new file mode 100644 index 00000000000000..7e8dff7191fe8e --- /dev/null +++ b/src/plugins/kibana_utils/public/core/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 './create_getter_setter'; +export * from './create_kibana_utils_core'; diff --git a/src/plugins/kibana_utils/public/core/saved_objects_client.ts b/src/plugins/kibana_utils/public/core/saved_objects_client.ts new file mode 100644 index 00000000000000..40407fea5d189f --- /dev/null +++ b/src/plugins/kibana_utils/public/core/saved_objects_client.ts @@ -0,0 +1,35 @@ +/* + * 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 { CoreStart } from '../../../../core/public'; +import { Get } from './create_getter_setter'; + +type CoreSavedObjectClient = CoreStart['savedObjects']['client']; + +export interface KUSavedObjectClient { + get: CoreSavedObjectClient['get']; +} + +export const createSavedObjectsClient = (getCoreStart: Get) => { + const savedObjectsClient: KUSavedObjectClient = { + get: (...args) => getCoreStart().savedObjects.client.get(...args), + }; + + return savedObjectsClient; +}; diff --git a/src/plugins/kibana_utils/public/core/state.ts b/src/plugins/kibana_utils/public/core/state.ts new file mode 100644 index 00000000000000..8ac6e4e0e58e6d --- /dev/null +++ b/src/plugins/kibana_utils/public/core/state.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. + */ + +import { createGetterSetter } from './create_getter_setter'; +import { CoreStart } from '../../../../core/public'; + +export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index bac0ef629789a2..3aaa6d28a9f64b 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -17,8 +17,12 @@ * under the License. */ +export * from './core'; +export * from './errors'; export * from './store'; export * from './parse'; export * from './render_complete'; +export * from './store'; export * from './errors'; export * from './field_mapping'; +export * from './storage'; diff --git a/src/legacy/ui/public/storage/__tests__/storage.js b/src/plugins/kibana_utils/public/storage/__tests__/storage.js similarity index 100% rename from src/legacy/ui/public/storage/__tests__/storage.js rename to src/plugins/kibana_utils/public/storage/__tests__/storage.js diff --git a/src/plugins/kibana_utils/public/storage/index.ts b/src/plugins/kibana_utils/public/storage/index.ts new file mode 100644 index 00000000000000..53956bf21cdf36 --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/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 { Storage } from './storage'; +export { IStorage, IStorageWrapper } from './types'; diff --git a/src/plugins/kibana_utils/public/storage/storage.ts b/src/plugins/kibana_utils/public/storage/storage.ts new file mode 100644 index 00000000000000..a7d3c5ac70074f --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/storage.ts @@ -0,0 +1,61 @@ +/* + * 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 { IStorage, IStorageWrapper } from './types'; + +export class Storage implements IStorageWrapper { + public store: IStorage; + + constructor(store: IStorage) { + this.store = store; + } + + public get = (key: string) => { + if (!this.store) { + return null; + } + + const storageItem = this.store.getItem(key); + if (storageItem === null) { + return null; + } + + try { + return JSON.parse(storageItem); + } catch (error) { + return null; + } + }; + + public set = (key: string, value: any) => { + try { + return this.store.setItem(key, JSON.stringify(value)); + } catch (e) { + return false; + } + }; + + public remove = (key: string) => { + return this.store.removeItem(key); + }; + + public clear = () => { + return this.store.clear(); + }; +} diff --git a/src/plugins/kibana_utils/public/storage/types.ts b/src/plugins/kibana_utils/public/storage/types.ts new file mode 100644 index 00000000000000..875bb44bcad17d --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/types.ts @@ -0,0 +1,32 @@ +/* + * 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 interface IStorageWrapper { + get: (key: string) => any; + set: (key: string, value: any) => void; + remove: (key: string) => any; + clear: () => void; +} + +export interface IStorage { + getItem: (key: string) => any; + setItem: (key: string, value: any) => void; + removeItem: (key: string) => any; + clear: () => void; +} diff --git a/src/plugins/kibana_utils/public/store/react.ts b/src/plugins/kibana_utils/public/store/react.ts index d561f9bb3cf34b..00861b2b0b8fef 100644 --- a/src/plugins/kibana_utils/public/store/react.ts +++ b/src/plugins/kibana_utils/public/store/react.ts @@ -86,10 +86,12 @@ export const createContext = < comparator?: Comparator ): Result => { const { state$, get } = useStore(); + /* eslint-disable react-hooks/exhaustive-deps */ const [observable$, unsubscribe] = useMemo( () => observableSelector(get(), state$, selector, comparator), [state$] ); + /* eslint-enable react-hooks/exhaustive-deps */ useLayoutEffect(() => unsubscribe, [observable$, unsubscribe]); const value = useObservable(observable$, selector(get())); return value; diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index fda25659d1226d..374acaaab39998 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -52,5 +52,8 @@ export class UiActionsPlugin implements Plugin return this.api; } - public stop() {} + public stop() { + this.actions.clear(); + this.triggers.clear(); + } } diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json new file mode 100644 index 00000000000000..cf79ce17293d61 --- /dev/null +++ b/src/plugins/visualizations/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "visualizations", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": [ + "expressions" + ] +} diff --git a/src/plugins/visualizations/public/expression_functions/range.ts b/src/plugins/visualizations/public/expression_functions/range.ts new file mode 100644 index 00000000000000..27c3654e2182ae --- /dev/null +++ b/src/plugins/visualizations/public/expression_functions/range.ts @@ -0,0 +1,61 @@ +/* + * 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 { ExpressionFunction, KibanaDatatable, Range } from '../../../expressions/public'; + +const name = 'range'; + +type Context = KibanaDatatable | null; + +interface Arguments { + from: number; + to: number; +} + +export const range = (): ExpressionFunction => ({ + name, + help: i18n.translate('visualizations.function.range.help', { + defaultMessage: 'Generates range object', + }), + type: 'range', + args: { + from: { + types: ['number'], + help: i18n.translate('visualizations.function.range.from.help', { + defaultMessage: 'Start of range', + }), + required: true, + }, + to: { + types: ['number'], + help: i18n.translate('visualizations.function.range.to.help', { + defaultMessage: 'End of range', + }), + required: true, + }, + }, + fn: (context, args) => { + return { + type: 'range', + from: args.from, + to: args.to, + }; + }, +}); diff --git a/src/plugins/visualizations/public/expression_functions/vis_dimension.ts b/src/plugins/visualizations/public/expression_functions/vis_dimension.ts new file mode 100644 index 00000000000000..4ad73ef5048741 --- /dev/null +++ b/src/plugins/visualizations/public/expression_functions/vis_dimension.ts @@ -0,0 +1,89 @@ +/* + * 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 { ExpressionFunction, KibanaDatatable } from '../../../expressions/public'; + +const name = 'visdimension'; + +type Context = KibanaDatatable | null; + +interface Arguments { + accessor: string | number; + format?: string; + formatParams?: string; +} + +type Return = any; + +export const visDimension = (): ExpressionFunction => ({ + name: 'visdimension', + help: i18n.translate('visualizations.function.visDimension.help', { + defaultMessage: 'Generates visConfig dimension object', + }), + type: 'vis_dimension', + context: { + types: ['kibana_datatable'], + }, + args: { + accessor: { + types: ['string', 'number'], + aliases: ['_'], + help: i18n.translate('visualizations.function.visDimension.accessor.help', { + defaultMessage: 'Column in your dataset to use (either column index or column name)', + }), + }, + format: { + types: ['string'], + default: 'string', + help: i18n.translate('visualizations.function.visDimension.format.help', { + defaultMessage: 'Format', + }), + }, + formatParams: { + types: ['string'], + default: '"{}"', + help: i18n.translate('visualizations.function.visDimension.formatParams.help', { + defaultMessage: 'Format params', + }), + }, + }, + fn: (context, args) => { + const accessor = + typeof args.accessor === 'number' + ? args.accessor + : context!.columns.find(c => c.id === args.accessor); + if (accessor === undefined) { + throw new Error( + i18n.translate('visualizations.function.visDimension.error.accessor', { + defaultMessage: 'Column name provided is invalid', + }) + ); + } + + return { + type: 'vis_dimension', + accessor, + format: { + id: args.format, + params: JSON.parse(args.formatParams!), + }, + }; + }, +}); diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index d8f7b5091eb8f6..2b4b10c8329a3a 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -17,4 +17,13 @@ * under the License. */ +import { PluginInitializerContext } from '../../../core/public'; +import { VisualizationsPublicPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new VisualizationsPublicPlugin(initializerContext); +} + +export { VisualizationsPublicPlugin as Plugin }; +export * from './plugin'; export * from './types'; diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts new file mode 100644 index 00000000000000..af7688d019f651 --- /dev/null +++ b/src/plugins/visualizations/public/mocks.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 { VisualizationsSetup, VisualizationsStart } from '.'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = undefined; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = undefined; + return startContract; +}; + +export const expressionsPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts new file mode 100644 index 00000000000000..cceb63122820d0 --- /dev/null +++ b/src/plugins/visualizations/public/plugin.ts @@ -0,0 +1,59 @@ +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsSetup, ExpressionsStart } from '../../expressions/public'; +import { range as rangeExpressionFunction } from './expression_functions/range'; +import { visDimension as visDimensionExpressionFunction } from './expression_functions/vis_dimension'; + +export interface VisualizationsSetupDeps { + expressions: ExpressionsSetup; +} + +export interface VisualizationsStartDeps { + expressions: ExpressionsStart; +} + +export type VisualizationsSetup = void; + +export type VisualizationsStart = void; + +export class VisualizationsPublicPlugin + implements + Plugin< + VisualizationsSetup, + VisualizationsStart, + VisualizationsSetupDeps, + VisualizationsStartDeps + > { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { expressions }: VisualizationsSetupDeps): VisualizationsSetup { + expressions.registerFunction(rangeExpressionFunction); + expressions.registerFunction(visDimensionExpressionFunction); + + return undefined; + } + + public start(core: CoreStart, { expressions }: VisualizationsStartDeps): VisualizationsStart { + return undefined; + } + + public stop() {} +} diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 52e2b869d5639d..1494c01166e200 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import { Client } from 'elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { createLegacyEsTestCluster, @@ -36,6 +36,7 @@ import { CliArgs, Env } from '../core/server/config'; import { LegacyObjectToConfigAdapter } from '../core/server/legacy'; import { Root } from '../core/server/root'; import KbnServer from '../legacy/server/kbn_server'; +import { CallCluster } from '../legacy/core_plugins/elasticsearch'; type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; @@ -147,6 +148,35 @@ export const request: Record< put: (root, path) => getSupertest(root, 'put', path), }; +export interface TestElasticsearchServer { + getStartTimeout: () => number; + start: (esArgs: string[], esEnvVars: Record) => Promise; + stop: () => Promise; + cleanup: () => Promise; + getClient: () => Client; + getCallCluster: () => CallCluster; + getUrl: () => string; +} + +export interface TestElasticsearchUtils { + stop: () => Promise; + es: TestElasticsearchServer; + hosts: string[]; + username: string; + password: string; +} + +export interface TestKibanaUtils { + root: Root; + kbnServer: KbnServer; + stop: () => Promise; +} + +export interface TestUtils { + startES: () => Promise; + startKibana: () => Promise; +} + /** * Creates an instance of the Root, including all of the core "legacy" plugins, * with default configuration tailored for unit tests, and starts es. @@ -161,7 +191,7 @@ export function createTestServers({ settings = {}, }: { adjustTimeout: (timeout: number) => void; - settings: { + settings?: { es?: { license: 'oss' | 'basic' | 'gold' | 'trial'; [key: string]: any; @@ -182,7 +212,7 @@ export function createTestServers({ */ users?: Array<{ username: string; password: string; roles: string[] }>; }; -}) { +}): TestUtils { if (!adjustTimeout) { throw new Error('adjustTimeout is required in order to avoid flaky tests'); } diff --git a/tasks/docker_docs.js b/tasks/docker_docs.js index 1c35f44eb65385..814a5a7e12bb9b 100644 --- a/tasks/docker_docs.js +++ b/tasks/docker_docs.js @@ -17,7 +17,7 @@ * under the License. */ -import rimraf from 'rimraf'; +import del from 'del'; import { join } from 'path'; import { execFileSync as exec } from 'child_process'; @@ -46,7 +46,7 @@ export default function (grunt) { ], { env })).trim(); grunt.log.write('Clearing old docs ... '); - rimraf.sync(htmlDocsDir); + del.sync(htmlDocsDir); grunt.log.writeln('done'); grunt.log.write('Copying new docs ... '); diff --git a/tasks/function_test_groups.js b/tasks/function_test_groups.js index 69b998c4f229bf..31656df2cb6447 100644 --- a/tasks/function_test_groups.js +++ b/tasks/function_test_groups.js @@ -58,12 +58,13 @@ grunt.registerTask( const done = this.async(); try { - const stats = JSON.parse(await execa.stderr(process.execPath, [ + const result = await execa(process.execPath, [ 'scripts/functional_test_runner', ...TEST_TAGS.map(tag => `--include-tag=${tag}`), '--config', 'test/functional/config.js', '--test-stats' - ])); + ]); + const stats = JSON.parse(result.stderr); if (stats.excludedTests.length > 0) { grunt.fail.fatal(` diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts new file mode 100644 index 00000000000000..e3f73ad4bcaf86 --- /dev/null +++ b/test/accessibility/apps/discover.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 { FtrProviderContext } from '../ftr_provider_context'; + +const FROM_TIME = '2015-09-19 06:31:44.000'; +const TO_TIME = '2015-09-23 18:31:44.000'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker']); + const a11y = getService('a11y'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + + describe('Discover', () => { + before(async () => { + await esArchiver.load('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.update({ + defaultIndex: 'logstash-*', + }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setAbsoluteRange(FROM_TIME, TO_TIME); + }); + + it('main view', async () => { + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/test/accessibility/apps/home.ts b/test/accessibility/apps/home.ts new file mode 100644 index 00000000000000..4df4d5f42c93d8 --- /dev/null +++ b/test/accessibility/apps/home.ts @@ -0,0 +1,35 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common']); + const a11y = getService('a11y'); + + describe('Kibana Home', () => { + before(async () => { + await PageObjects.common.navigateToApp('home'); + }); + + it('Kibana Home view', async () => { + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/test/accessibility/apps/management.ts b/test/accessibility/apps/management.ts new file mode 100644 index 00000000000000..842f4ecbafa9ef --- /dev/null +++ b/test/accessibility/apps/management.ts @@ -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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'settings']); + const a11y = getService('a11y'); + + describe('Management', () => { + before(async () => { + await PageObjects.common.navigateToApp('settings'); + }); + + it('main view', async () => { + await a11y.testAppSnapshot(); + }); + + it('index pattern page', async () => { + await PageObjects.settings.clickKibanaIndexPatterns(); + await a11y.testAppSnapshot(); + }); + + it('Single indexpattern view', async () => { + await PageObjects.settings.clickIndexPatternLogstash(); + await a11y.testAppSnapshot(); + }); + + it('Saved objects view', async () => { + await PageObjects.settings.clickKibanaSavedObjects(); + await a11y.testAppSnapshot(); + }); + + it('Advanced settings', async () => { + await PageObjects.settings.clickKibanaSettings(); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/test/accessibility/config.ts b/test/accessibility/config.ts new file mode 100644 index 00000000000000..d73a73820a1172 --- /dev/null +++ b/test/accessibility/config.ts @@ -0,0 +1,42 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services } from './services'; +import { pageObjects } from './page_objects'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../functional/config')); + + return { + ...functionalConfig.getAll(), + + testFiles: [ + require.resolve('./apps/discover'), + require.resolve('./apps/management'), + require.resolve('./apps/home'), + ], + pageObjects, + services, + + junit: { + reportName: 'Accessibility Tests', + }, + }; +} diff --git a/test/accessibility/ftr_provider_context.d.ts b/test/accessibility/ftr_provider_context.d.ts new file mode 100644 index 00000000000000..22df3b16150a43 --- /dev/null +++ b/test/accessibility/ftr_provider_context.d.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. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { pageObjects } from './page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/test/accessibility/page_objects.ts b/test/accessibility/page_objects.ts new file mode 100644 index 00000000000000..151b6b34781b84 --- /dev/null +++ b/test/accessibility/page_objects.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 { pageObjects } from '../functional/page_objects'; diff --git a/test/accessibility/services/a11y/a11y.ts b/test/accessibility/services/a11y/a11y.ts new file mode 100644 index 00000000000000..7adfe7ebfcc7d7 --- /dev/null +++ b/test/accessibility/services/a11y/a11y.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 chalk from 'chalk'; +import testSubjectToCss from '@kbn/test-subj-selector'; + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { AxeReport, printResult } from './axe_report'; +// @ts-ignore JS that is run in browser as is +import { analyzeWithAxe, analyzeWithAxeWithClient } from './analyze_with_axe'; + +interface AxeContext { + include?: string[]; + exclude?: string[][]; +} + +interface TestOptions { + excludeTestSubj?: string | string[]; +} + +export const normalizeResult = (report: any) => { + if (report.error) { + throw report.error; + } + + return report.result as false | AxeReport; +}; + +export function A11yProvider({ getService }: FtrProviderContext) { + const browser = getService('browser'); + const Wd = getService('__webdriver__'); + const log = getService('log'); + + /** + * Accessibility testing service using the Axe (https://www.deque.com/axe/) + * toolset to validate a11y rules similar to ESLint. In order to test against + * the rules we must load up the UI and feed a full HTML snapshot into Axe. + */ + return new (class Accessibility { + public async testAppSnapshot(options: TestOptions = {}) { + const context = this.getAxeContext(true, options.excludeTestSubj); + const report = await this.captureAxeReport(context); + await this.testAxeReport(report); + } + + public async testGlobalSnapshot(options: TestOptions = {}) { + const context = this.getAxeContext(false, options.excludeTestSubj); + const report = await this.captureAxeReport(context); + await this.testAxeReport(report); + } + + private getAxeContext(global: boolean, excludeTestSubj?: string | string[]): AxeContext { + return { + include: global ? undefined : [testSubjectToCss('appA11yRoot')], + exclude: ([] as string[]) + .concat(excludeTestSubj || []) + .map(ts => [testSubjectToCss(ts)]) + .concat([['.ace_scrollbar']]), + }; + } + + private testAxeReport(report: AxeReport) { + const errorMsgs = []; + + for (const result of report.incomplete) { + // these items require human review and can't be definitively validated + log.warning(printResult(chalk.yellow('UNABLE TO VALIDATE'), result)); + } + + for (const result of report.violations) { + errorMsgs.push(printResult(chalk.red('VIOLATION'), result)); + } + + if (errorMsgs.length) { + throw new Error(`a11y report:\n${errorMsgs.join('\n')}`); + } + } + + private async captureAxeReport(context: AxeContext): Promise { + const axeOptions = { + reporter: 'v2', + runOnly: ['wcag2a', 'wcag2aa'], + rules: { + 'color-contrast': { + enabled: false, + }, + }, + }; + + await (Wd.driver.manage() as any).setTimeouts({ + ...(await (Wd.driver.manage() as any).getTimeouts()), + script: 600000, + }); + + const report = normalizeResult( + await browser.executeAsync(analyzeWithAxe, context, axeOptions) + ); + + if (report !== false) { + return report; + } + + const withClientReport = normalizeResult( + await browser.executeAsync(analyzeWithAxeWithClient, context, axeOptions) + ); + + if (withClientReport === false) { + throw new Error('Attempted to analyze with axe but failed to load axe client'); + } + + return withClientReport; + } + })(); +} diff --git a/test/accessibility/services/a11y/analyze_with_axe.js b/test/accessibility/services/a11y/analyze_with_axe.js new file mode 100644 index 00000000000000..5cba55a29f8a4d --- /dev/null +++ b/test/accessibility/services/a11y/analyze_with_axe.js @@ -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 { readFileSync } from 'fs'; + +export function analyzeWithAxe(context, options, callback) { + Promise.resolve() + .then(() => { + if (window.axe) { + return window.axe.run(context, options); + } + + // return a false report to trigger analyzeWithAxeWithClient + return false; + }) + .then(result => callback({ result }), error => callback({ error })); +} + +export const analyzeWithAxeWithClient = ` + ${readFileSync(require.resolve('axe-core/axe.js'), 'utf8')} + + return (${analyzeWithAxe.toString()}).apply(null, arguments); +`; diff --git a/test/accessibility/services/a11y/axe_report.ts b/test/accessibility/services/a11y/axe_report.ts new file mode 100644 index 00000000000000..ba1e8c739079da --- /dev/null +++ b/test/accessibility/services/a11y/axe_report.ts @@ -0,0 +1,69 @@ +/* + * 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. + */ + +type AxeImpact = 'minor' | 'moderate' | 'serious' | 'critical'; + +type AxeRelatedNodes = Array<{ + data: any; + id: string; + impact: AxeImpact; + message: string; + relatedNodes: []; +}>; + +export interface AxeResult { + /* Rule description */ + description: string; + /* rule title/error message */ + help: string; + /* documentation url */ + helpUrl: string; + /* rule id */ + id: string; + /* severity level */ + impact?: AxeImpact; + /* tags used to group rules */ + tags: string[]; + /* nodes grouped in this result */ + nodes: Array<{ + all: AxeRelatedNodes; + any: AxeRelatedNodes; + none: AxeRelatedNodes; + + html: string; + impact: AxeImpact; + target: string[]; + }>; +} + +export type AxeResultGroup = AxeResult[]; + +export interface AxeReport { + inapplicable: AxeResultGroup; + passes: AxeResultGroup; + incomplete: AxeResultGroup; + violations: AxeResultGroup; +} + +export const printResult = (title: string, result: AxeResult) => ` +${title} + [${result.id}]: ${result.description} + Help: ${result.helpUrl} + Elements: + - ${result.nodes.map(node => node.target).join('\n - ')}`; diff --git a/test/accessibility/services/a11y/index.ts b/test/accessibility/services/a11y/index.ts new file mode 100644 index 00000000000000..7baa17c1eafa33 --- /dev/null +++ b/test/accessibility/services/a11y/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 { A11yProvider } from './a11y'; diff --git a/test/accessibility/services/index.ts b/test/accessibility/services/index.ts new file mode 100644 index 00000000000000..98d1628732f5c8 --- /dev/null +++ b/test/accessibility/services/index.ts @@ -0,0 +1,26 @@ +/* + * 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 { services as kibanaFunctionalServices } from '../../functional/services'; +import { A11yProvider } from './a11y'; + +export const services = { + ...kibanaFunctionalServices, + a11y: A11yProvider, +}; diff --git a/test/api_integration/apis/core/index.js b/test/api_integration/apis/core/index.js index e5da4e4730662f..d617b2ad073511 100644 --- a/test/api_integration/apis/core/index.js +++ b/test/api_integration/apis/core/index.js @@ -16,45 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); - describe('core', () => { - describe('request context', () => { - it('provides access to elasticsearch', async () => ( - await supertest - .get('/requestcontext/elasticsearch') - .expect(200, 'Elasticsearch: true') - )); + describe('core request context', () => { + it('provides access to elasticsearch', async () => ( + await supertest + .get('/requestcontext/elasticsearch') + .expect(200, 'Elasticsearch: true') + )); - it('provides access to SavedObjects client', async () => ( - await supertest - .get('/requestcontext/savedobjectsclient') - .expect(200, 'SavedObjects client: {"page":1,"per_page":20,"total":0,"saved_objects":[]}') - )); - }); - - describe('compression', () => { - it(`uses compression when there isn't a referer`, async () => { - await supertest - .get('/app/kibana') - .set('accept-encoding', 'gzip') - .then(response => { - expect(response.headers).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`doesn't use compression when there is a referer`, async () => { - await supertest - .get('/app/kibana') - .set('accept-encoding', 'gzip') - .set('referer', 'https://www.google.com') - .then(response => { - expect(response.headers).not.to.have.property('content-encoding'); - }); - }); - }); + it('provides access to SavedObjects client', async () => ( + await supertest + .get('/requestcontext/savedobjectsclient') + .expect(200, 'SavedObjects client: {"page":1,"per_page":20,"total":0,"saved_objects":[]}') + )); }); } diff --git a/test/api_integration/apis/index.js b/test/api_integration/apis/index.js index de36ee678b10ed..9f2672959390ce 100644 --- a/test/api_integration/apis/index.js +++ b/test/api_integration/apis/index.js @@ -34,6 +34,5 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./status')); loadTestFile(require.resolve('./stats')); loadTestFile(require.resolve('./ui_metric')); - loadTestFile(require.resolve('./core')); }); } diff --git a/test/api_integration/apis/index_patterns/es_errors/errors.js b/test/api_integration/apis/index_patterns/es_errors/errors.js index 7e04e3f7204d71..4e50b965211c29 100644 --- a/test/api_integration/apis/index_patterns/es_errors/errors.js +++ b/test/api_integration/apis/index_patterns/es_errors/errors.js @@ -26,7 +26,7 @@ import { createNoMatchingIndicesError, isNoMatchingIndicesError, convertEsError -} from '../../../../../src/legacy/server/index_patterns/service/lib/errors'; +} from '../../../../../src/plugins/data/server/index_patterns/fetcher/lib/errors'; import { getIndexNotFoundError, diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/params.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/params.js index 37916d3bee3759..2bd574d669e20e 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/params.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/params.js @@ -20,7 +20,7 @@ export default function ({ getService }) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - const chance = getService('chance'); + const randomness = getService('randomness'); describe('params', () => { before(() => esArchiver.load('index_patterns/basic_index')); @@ -63,8 +63,8 @@ export default function ({ getService }) { supertest .get('/api/index_patterns/_fields_for_wildcard') .query({ - pattern: chance.word(), - [chance.word()]: chance.word(), + pattern: randomness.word(), + [randomness.word()]: randomness.word(), }) .expect(400)); }); diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index d909b51e94ac04..bc70339bd1a665 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -61,8 +61,7 @@ export default function ({ getService }) { aggregatable: true, name: 'baz.keyword', readFromDocValues: true, - parent: 'baz', - subType: 'multi', + subType: { multi: { parent: 'baz' } } }, { type: 'number', @@ -72,6 +71,21 @@ export default function ({ getService }) { name: 'foo', readFromDocValues: true, }, + { + aggregatable: true, + esTypes: [ + 'keyword' + ], + name: 'nestedField.child', + readFromDocValues: true, + searchable: true, + subType: { + nested: { + path: 'nestedField' + } + }, + type: 'string', + }, ], }) .then(ensureFieldsAreSorted)); @@ -124,8 +138,7 @@ export default function ({ getService }) { aggregatable: true, name: 'baz.keyword', readFromDocValues: true, - parent: 'baz', - subType: 'multi', + subType: { multi: { parent: 'baz' } } }, { aggregatable: false, @@ -142,6 +155,21 @@ export default function ({ getService }) { name: 'foo', readFromDocValues: true, }, + { + aggregatable: true, + esTypes: [ + 'keyword' + ], + name: 'nestedField.child', + readFromDocValues: true, + searchable: true, + subType: { + nested: { + path: 'nestedField' + } + }, + type: 'string', + }, ], }) .then(ensureFieldsAreSorted)); diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index a1c84a766a1167..7b2b15d298ce02 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -141,9 +141,7 @@ export default function ({ getService }) { id: '91200a00-9efd-11e7-acb3-3dab96693fab', } ], - migrationVersion: { - visualization: '7.3.1', - }, + migrationVersion: resp.body.saved_objects[0].migrationVersion, updated_at: '2017-09-21T18:51:23.794Z', version: 'WzIsMV0=', }, diff --git a/test/api_integration/apis/suggestions/suggestions.js b/test/api_integration/apis/suggestions/suggestions.js index 80b6febb81d7b6..abacf13c7aa064 100644 --- a/test/api_integration/apis/suggestions/suggestions.js +++ b/test/api_integration/apis/suggestions/suggestions.js @@ -22,8 +22,14 @@ export default function ({ getService }) { const supertest = getService('supertest'); describe('Suggestions API', function () { - before(() => esArchiver.load('index_patterns/basic_index')); - after(() => esArchiver.unload('index_patterns/basic_index')); + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + await esArchiver.load('index_patterns/basic_kibana'); + }); + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + await esArchiver.unload('index_patterns/basic_kibana'); + }); it('should return 200 with special characters', () => ( supertest @@ -34,5 +40,15 @@ export default function ({ getService }) { }) .expect(200) )); + + it('should support nested fields', () => ( + supertest + .post('/api/kibana/suggestions/values/basic_index') + .send({ + field: 'nestedField.child', + query: 'nes' + }) + .expect(200, ['nestedValue']) + )); }); } diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz index edae3bfdaa2ea9..ec085ec813240c 100644 Binary files a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz and b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json index 5d0d4260109533..338b31dc681ae3 100644 --- a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json @@ -24,8 +24,18 @@ }, "foo": { "type": "long" + }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } } } } } -} \ No newline at end of file +} + + diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/data.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/data.json new file mode 100644 index 00000000000000..54276b59dcc231 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/data.json @@ -0,0 +1,15 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "index-pattern:91200a00-9efd-11e7-acb3-3dab96693fab", + "source": { + "type": "index-pattern", + "updated_at": "2017-09-21T18:49:16.270Z", + "index-pattern": { + "title": "basic_index", + "fields": "[{\"name\":\"bar\",\"type\":\"boolean\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"baz\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"baz.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"foo\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nestedField.child\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"nested\":{\"path\":\"nestedField\"}}}]" + } + } + } +} diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/mappings.json new file mode 100644 index 00000000000000..99264d7ebbff85 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/mappings.json @@ -0,0 +1,253 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + } + } +} diff --git a/test/api_integration/services/chance.js b/test/api_integration/services/chance.js deleted file mode 100644 index 3c5d9dc704aae0..00000000000000 --- a/test/api_integration/services/chance.js +++ /dev/null @@ -1,29 +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 Chance from 'chance'; - -export function ChanceProvider({ getService }) { - const log = getService('log'); - - const seed = Date.now(); - log.debug('randomness seed: %j', seed); - - return new Chance(seed); -} diff --git a/test/api_integration/services/index.ts b/test/api_integration/services/index.ts index d0fcd94a6a2047..573450cae534a4 100644 --- a/test/api_integration/services/index.ts +++ b/test/api_integration/services/index.ts @@ -22,14 +22,11 @@ import { services as commonServices } from '../../common/services'; // @ts-ignore not TS yet import { KibanaSupertestProvider, ElasticsearchSupertestProvider } from './supertest'; -// @ts-ignore not TS yet -import { ChanceProvider } from './chance'; - export const services = { es: commonServices.es, esArchiver: commonServices.esArchiver, retry: commonServices.retry, supertest: KibanaSupertestProvider, esSupertest: ElasticsearchSupertestProvider, - chance: ChanceProvider, + randomness: commonServices.randomness, }; diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 47d93d811e3f6b..225aacc1c98958 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -21,10 +21,12 @@ import { LegacyEsProvider } from './legacy_es'; import { EsArchiverProvider } from './es_archiver'; import { KibanaServerProvider } from './kibana_server'; import { RetryProvider } from './retry'; +import { RandomnessProvider } from './randomness'; export const services = { es: LegacyEsProvider, esArchiver: EsArchiverProvider, kibanaServer: KibanaServerProvider, retry: RetryProvider, + randomness: RandomnessProvider, }; diff --git a/test/common/services/randomness.ts b/test/common/services/randomness.ts new file mode 100644 index 00000000000000..2a49703e92c58a --- /dev/null +++ b/test/common/services/randomness.ts @@ -0,0 +1,89 @@ +/* + * 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 Chance from 'chance'; + +import { FtrProviderContext } from '../ftr_provider_context'; + +interface CharOptions { + pool?: string; + alpha?: boolean; + numeric?: boolean; + symbols?: boolean; + casing?: 'lower' | 'upper'; +} + +interface StringOptions extends CharOptions { + length?: number; +} + +interface NumberOptions { + min?: number; + max?: number; +} + +export function RandomnessProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + + const seed = Date.now(); + log.debug('randomness seed: %j', seed); + + const chance = new Chance(seed); + + return new (class RandomnessService { + /** + * Generate a random natural number + * + * range: 0 to 9007199254740991 + * + */ + naturalNumber(options?: NumberOptions) { + return chance.natural(options); + } + + /** + * Generate a random integer + */ + integer(options?: NumberOptions) { + return chance.integer(options); + } + + /** + * Generate a random number, defaults to at least 4 and no more than 8 syllables + */ + word(options: { syllables?: number } = {}) { + const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options; + + return chance.word({ + syllables, + }); + } + + /** + * Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters + */ + string(options: StringOptions = {}) { + return chance.string({ + length: this.naturalNumber({ min: 8, max: 15 }), + ...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}), + ...options, + }); + } + })(); +} diff --git a/test/functional/apps/dashboard/dashboard_state.js b/test/functional/apps/dashboard/dashboard_state.js index 6dfef4c54c66b5..b9ad47e428e829 100644 --- a/test/functional/apps/dashboard/dashboard_state.js +++ b/test/functional/apps/dashboard/dashboard_state.js @@ -24,7 +24,7 @@ import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../page_objects/dash // eslint-disable-next-line import { DEFAULT_PANEL_WIDTH -} from '../../../../src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_constants'; +} from '../../../../src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_constants'; export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['dashboard', 'visualize', 'header', 'discover']); diff --git a/test/functional/apps/dashboard/panel_controls.js b/test/functional/apps/dashboard/panel_controls.js index b67e1dd7b2eb1e..063ad1f79efae2 100644 --- a/test/functional/apps/dashboard/panel_controls.js +++ b/test/functional/apps/dashboard/panel_controls.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; -import { PIE_CHART_VIS_NAME } from '../../page_objects/dashboard_page'; +import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME, LINE_CHART_VIS_NAME } from '../../page_objects/dashboard_page'; import { VisualizeConstants } from '../../../../src/legacy/core_plugins/kibana/public/visualize/visualize_constants'; @@ -28,6 +28,8 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardReplacePanel = getService('dashboardReplacePanel'); + const dashboardVisualizations = getService('dashboardVisualizations'); const renderable = getService('renderable'); const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'discover']); const dashboardName = 'Dashboard Panel Controls Test'; @@ -44,6 +46,62 @@ export default function ({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); }); + describe('visualization object replace flyout', () => { + let intialDimensions; + before(async () => { + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); + await dashboardAddPanel.addVisualization(LINE_CHART_VIS_NAME); + intialDimensions = await PageObjects.dashboard.getPanelDimensions(); + }); + + after(async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + }); + + it('replaces old panel with selected panel', async () => { + await dashboardPanelActions.replacePanelByTitle(PIE_CHART_VIS_NAME); + await dashboardReplacePanel.replaceEmbeddable(AREA_CHART_VIS_NAME); + await PageObjects.header.waitUntilLoadingHasFinished(); + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + expect(panelTitles.length).to.be(2); + expect(panelTitles[0]).to.be(AREA_CHART_VIS_NAME); + }); + + it('replaces selected visualization with old dimensions', async () => { + const newDimensions = await PageObjects.dashboard.getPanelDimensions(); + expect(intialDimensions[0]).to.eql(newDimensions[0]); + }); + + it('replaced panel persisted correctly when dashboard is hard refreshed', async () => { + const currentUrl = await browser.getCurrentUrl(); + await browser.get(currentUrl, true); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + expect(panelTitles.length).to.be(2); + expect(panelTitles[0]).to.be(AREA_CHART_VIS_NAME); + }); + + it('replaced panel with saved search', async () => { + const replacedSearch = 'replaced saved search'; + await dashboardVisualizations.createSavedSearch({ name: replacedSearch, fields: ['bytes', 'agent'] }); + await PageObjects.header.clickDashboard(); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); + if (inViewMode) { + await PageObjects.dashboard.switchToEditMode(); + } + await dashboardPanelActions.replacePanelByTitle(AREA_CHART_VIS_NAME); + await dashboardReplacePanel.replaceEmbeddable(replacedSearch, 'search'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + expect(panelTitles.length).to.be(2); + expect(panelTitles[0]).to.be(replacedSearch); + }); + }); + describe('panel edit controls', function () { before(async () => { await PageObjects.dashboard.clickNewDashboard(); @@ -67,6 +125,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectExistsEditPanelAction(); + await dashboardPanelActions.expectExistsReplacePanelAction(); await dashboardPanelActions.expectExistsRemovePanelAction(); }); @@ -80,6 +139,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectExistsEditPanelAction(); + await dashboardPanelActions.expectExistsReplacePanelAction(); await dashboardPanelActions.expectExistsRemovePanelAction(); // Get rid of the timestamp in the url. @@ -94,6 +154,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.clickExpandPanelToggle(); await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectMissingEditPanelAction(); + await dashboardPanelActions.expectMissingReplacePanelAction(); await dashboardPanelActions.expectMissingRemovePanelAction(); }); @@ -101,6 +162,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.dashboard.switchToEditMode(); await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectExistsEditPanelAction(); + await dashboardPanelActions.expectExistsReplacePanelAction(); await dashboardPanelActions.expectMissingRemovePanelAction(); await dashboardPanelActions.clickExpandPanelToggle(); }); @@ -126,13 +188,18 @@ export default function ({ getService, getPageObjects }) { }); describe('saved search object edit menu', () => { + const searchName = 'my search'; before(async () => { await PageObjects.header.clickDiscover(); - await PageObjects.discover.clickFieldListItemAdd('bytes'); - await PageObjects.discover.saveSearch('my search'); + await PageObjects.discover.clickNewSearchButton(); + await dashboardVisualizations.createSavedSearch({ name: searchName, fields: ['bytes'] }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.header.clickDashboard(); - await dashboardAddPanel.addSavedSearch('my search'); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); + if (inViewMode) { + await PageObjects.dashboard.switchToEditMode(); + } + await dashboardAddPanel.addSavedSearch(searchName); const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.be(1); @@ -143,7 +210,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.clickEdit(); await PageObjects.header.waitUntilLoadingHasFinished(); const queryName = await PageObjects.discover.getCurrentQueryName(); - expect(queryName).to.be('my search'); + expect(queryName).to.be(searchName); }); it('deletes the saved search when delete link is clicked', async () => { diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 9f37feef781cae..9d3f95e28942a8 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -26,6 +26,7 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); const filterBar = getService('filterBar'); + const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -106,9 +107,9 @@ export default function ({ getService, getPageObjects }) { await PageObjects.discover.brushHistogram(); const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); - expect(Math.round(newDurationHours)).to.be(108); + expect(Math.round(newDurationHours)).to.be(25); const rowData = await PageObjects.discover.getDocTableField(1); - expect(rowData).to.have.string('Sep 22, 2015 @ 23:50:13.253'); + expect(Date.parse(rowData)).to.be.within(Date.parse('Sep 20, 2015 @ 22:00:00.000'), Date.parse('Sep 20, 2015 @ 23:30:00.000')); }); it('should show correct initial chart interval of Auto', async function () { @@ -154,6 +155,26 @@ export default function ({ getService, getPageObjects }) { }); }); + describe('nested query', () => { + + before(async () => { + log.debug('setAbsoluteRangeForAnotherQuery'); + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + it('should support querying on nested fields', async function () { + await queryBar.setQuery('nestedField:{ child: nestedValue }'); + await queryBar.submitQuery(); + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be( + '1' + ); + }); + }); + + }); + describe('filter editor', function () { it('should add a phrases filter', async function () { await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); diff --git a/test/functional/apps/visualize/_shared_item.js b/test/functional/apps/visualize/_shared_item.js index efd534f0350931..9fe38be15a7416 100644 --- a/test/functional/apps/visualize/_shared_item.js +++ b/test/functional/apps/visualize/_shared_item.js @@ -24,8 +24,7 @@ export default function ({ getService, getPageObjects }) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'visualize']); - // https://github.com/elastic/kibana/issues/37130 - describe.skip('data-shared-item', function indexPatternCreation() { + describe('data-shared-item', function indexPatternCreation() { before(async function () { log.debug('navigateToApp visualize'); await PageObjects.common.navigateToApp('visualize'); diff --git a/test/functional/fixtures/es_archiver/discover/data.json.gz b/test/functional/fixtures/es_archiver/discover/data.json.gz index c607f211d37c24..047d890f6d4101 100644 Binary files a/test/functional/fixtures/es_archiver/discover/data.json.gz and b/test/functional/fixtures/es_archiver/discover/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz b/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz index 2cef0717385267..a212c34e2ead64 100644 Binary files a/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz and b/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/logstash_functional/mappings.json b/test/functional/fixtures/es_archiver/logstash_functional/mappings.json index dcfafa2612c5bf..010abff9cf6a9f 100644 --- a/test/functional/fixtures/es_archiver/logstash_functional/mappings.json +++ b/test/functional/fixtures/es_archiver/logstash_functional/mappings.json @@ -153,6 +153,14 @@ } } }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } + }, "phpmemory": { "type": "long" }, @@ -518,6 +526,14 @@ } } }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } + }, "phpmemory": { "type": "long" }, @@ -883,6 +899,14 @@ } } }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } + }, "phpmemory": { "type": "long" }, @@ -1091,4 +1115,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js index 4f078d4b7a4384..ca141114f976dc 100644 --- a/test/functional/page_objects/dashboard_page.js +++ b/test/functional/page_objects/dashboard_page.js @@ -22,6 +22,7 @@ import { DashboardConstants } from '../../../src/legacy/core_plugins/kibana/publ export const PIE_CHART_VIS_NAME = 'Visualization PieChart'; export const AREA_CHART_VIS_NAME = 'Visualization漢字 AreaChart'; +export const LINE_CHART_VIS_NAME = 'Visualization漢字 LineChart'; export function DashboardPageProvider({ getService, getPageObjects }) { const log = getService('log'); @@ -499,7 +500,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) { { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, { name: AREA_CHART_VIS_NAME, description: 'AreaChart' }, { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, - { name: 'Visualization漢字 LineChart', description: 'LineChart' }, + { name: LINE_CHART_VIS_NAME, description: 'LineChart' }, { name: 'Visualization TileMap', description: 'TileMap' }, { name: 'Visualization MetricChart', description: 'MetricChart' } ]; diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index 4654b85f9c7c45..8d12b2117a2149 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -283,19 +283,13 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { } async openSidebarFieldFilter() { - const fieldFilterFormExists = await testSubjects.exists('discoverFieldFilter'); - if (!fieldFilterFormExists) { - await testSubjects.click('toggleFieldFilterButton'); - await testSubjects.existOrFail('discoverFieldFilter'); - } + await testSubjects.click('toggleFieldFilterButton'); + await testSubjects.existOrFail('filterSelectionPanel'); } async closeSidebarFieldFilter() { - const fieldFilterFormExists = await testSubjects.exists('discoverFieldFilter'); - if (fieldFilterFormExists) { - await testSubjects.click('toggleFieldFilterButton'); - await testSubjects.missingOrFail('discoverFieldFilter', { allowHidden: true }); - } + await testSubjects.click('toggleFieldFilterButton'); + await testSubjects.missingOrFail('filterSelectionPanel', { allowHidden: true }); } } diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 4060e3dd7800f2..97e02958f3787f 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -461,10 +461,7 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { ); } - public async executeAsync( - fn: string | ((...args: A) => R), - ...args: A - ): Promise { + public async executeAsync(fn: string | ((...args: any[]) => R), ...args: any[]): Promise { return await driver.executeAsyncScript( fn, ...cloneDeep(args, arg => { diff --git a/test/functional/services/dashboard/index.js b/test/functional/services/dashboard/index.js index b2de690157535f..bb9b861682907d 100644 --- a/test/functional/services/dashboard/index.js +++ b/test/functional/services/dashboard/index.js @@ -20,5 +20,6 @@ export { DashboardVisualizationProvider } from './visualizations'; export { DashboardExpectProvider } from './expectations'; export { DashboardAddPanelProvider } from './add_panel'; +export { DashboardReplacePanelProvider } from './replace_panel'; export { DashboardPanelActionsProvider } from './panel_actions'; diff --git a/test/functional/services/dashboard/panel_actions.js b/test/functional/services/dashboard/panel_actions.js index b7327f4af6d174..6826ea1ff17150 100644 --- a/test/functional/services/dashboard/panel_actions.js +++ b/test/functional/services/dashboard/panel_actions.js @@ -19,6 +19,7 @@ const REMOVE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-deletePanel'; const EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-editPanel'; +const REPLACE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-replacePanel'; const TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-togglePanel'; const CUSTOMIZE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-CUSTOMIZE_PANEL_ACTION_ID'; const OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ = 'embeddablePanelToggleMenuIcon'; @@ -87,6 +88,16 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); } + async replacePanelByTitle(title) { + log.debug(`replacePanel(${title})`); + let panelOptions = null; + if (title) { + panelOptions = await this.getPanelHeading(title); + } + await this.openContextMenu(panelOptions); + await testSubjects.click(REPLACE_PANEL_DATA_TEST_SUBJ); + } + async openInspectorByTitle(title) { const header = await this.getPanelHeading(title); await this.openInspector(header); @@ -112,11 +123,21 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.existOrFail(EDIT_PANEL_DATA_TEST_SUBJ); } + async expectExistsReplacePanelAction() { + log.debug('expectExistsEditPanelAction'); + await testSubjects.existOrFail(REPLACE_PANEL_DATA_TEST_SUBJ); + } + async expectMissingEditPanelAction() { log.debug('expectMissingEditPanelAction'); await testSubjects.missingOrFail(EDIT_PANEL_DATA_TEST_SUBJ); } + async expectMissingReplacePanelAction() { + log.debug('expectMissingEditPanelAction'); + await testSubjects.missingOrFail(REPLACE_PANEL_DATA_TEST_SUBJ); + } + async expectExistsToggleExpandAction() { log.debug('expectExistsToggleExpandAction'); await testSubjects.existOrFail(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ); diff --git a/test/functional/services/dashboard/replace_panel.js b/test/functional/services/dashboard/replace_panel.js new file mode 100644 index 00000000000000..b3ea6f9cf21ed8 --- /dev/null +++ b/test/functional/services/dashboard/replace_panel.js @@ -0,0 +1,102 @@ +/* + * 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 function DashboardReplacePanelProvider({ getService }) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const flyout = getService('flyout'); + + return new class DashboardReplacePanel { + async toggleFilterPopover() { + log.debug('DashboardReplacePanel.toggleFilter'); + await testSubjects.click('savedObjectFinderFilterButton'); + } + + async toggleFilter(type) { + log.debug(`DashboardReplacePanel.replaceToFilter(${type})`); + await this.waitForListLoading(); + await this.toggleFilterPopover(); + await testSubjects.click(`savedObjectFinderFilter-${type}`); + await this.toggleFilterPopover(); + } + + async isReplacePanelOpen() { + log.debug('DashboardReplacePanel.isReplacePanelOpen'); + return await testSubjects.exists('dashboardReplacePanel'); + } + + async ensureReplacePanelIsShowing() { + log.debug('DashboardReplacePanel.ensureReplacePanelIsShowing'); + const isOpen = await this.isReplacePanelOpen(); + if (!isOpen) { + throw new Error('Replace panel is not open, trying again.'); + } + } + + async waitForListLoading() { + await testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); + } + + async closeReplacePanel() { + await flyout.ensureClosed('dashboardReplacePanel'); + } + + async replaceSavedSearch(searchName) { + return this.replaceEmbeddable(searchName, 'search'); + } + + async replaceSavedSearches(searches) { + for (const name of searches) { + await this.replaceSavedSearch(name); + } + } + + async replaceVisualization(vizName) { + return this.replaceEmbeddable(vizName, 'visualization'); + } + + async replaceEmbeddable(embeddableName, embeddableType) { + log.debug(`DashboardReplacePanel.replaceEmbeddable, name: ${embeddableName}, type: ${embeddableType}`); + await this.ensureReplacePanelIsShowing(); + if (embeddableType) { + await this.toggleFilter(embeddableType); + } + await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); + await testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); + await testSubjects.exists('addObjectToDashboardSuccess'); + await this.closeReplacePanel(); + return embeddableName; + } + + async filterEmbeddableNames(name) { + // The search input field may be disabled while the table is loading so wait for it + await this.waitForListLoading(); + await testSubjects.setValue('savedObjectFinderSearchInput', name); + await this.waitForListLoading(); + } + + async panelReplaceLinkExists(name) { + log.debug(`DashboardReplacePanel.panelReplaceLinkExists(${name})`); + await this.ensureReplacePanelIsShowing(); + await this.filterEmbeddableNames(`"${name}"`); + return await testSubjects.exists(`savedObjectTitle${name.split(' ').join('-')}`); + } + }; +} diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 2566c3c87334c4..6098e9931f299f 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -24,6 +24,7 @@ import { BrowserProvider } from './browser'; import { ComboBoxProvider } from './combo_box'; import { DashboardAddPanelProvider, + DashboardReplacePanelProvider, DashboardExpectProvider, DashboardPanelActionsProvider, DashboardVisualizationProvider, @@ -66,6 +67,7 @@ export const services = { failureDebugging: FailureDebuggingProvider, visualizeListingTable: VisualizeListingTableProvider, dashboardAddPanel: DashboardAddPanelProvider, + dashboardReplacePanel: DashboardReplacePanelProvider, dashboardPanelActions: DashboardPanelActionsProvider, flyout: FlyoutProvider, comboBox: ComboBoxProvider, diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 4dc608542c3c3f..b30a0e50886d12 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -128,6 +128,8 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { .manage() .window() .setRect({ width, height }); + await driver.executeScript('window.sessionStorage.clear();'); + await driver.executeScript('window.localStorage.clear();'); }); lifecycle.on('cleanup', async () => { diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 5ebec6bde6ebbd..201354fff48efa 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -75,13 +75,23 @@ async function attemptToCreateCommand( case 'chrome': { const chromeCapabilities = Capabilities.chrome(); const chromeOptions = [ - 'disable-translate', - 'new-window', + // Disables the sandbox for all process types that are normally sandboxed. 'no-sandbox', + // Launches URL in new browser window. + 'new-window', + // By default, file:// URIs cannot read other file:// URIs. This is an override for developers who need the old behavior for testing. 'allow-file-access-from-files', + // Use fake device for Media Stream to replace actual camera and microphone. 'use-fake-device-for-media-stream', + // Bypass the media stream infobar by selecting the default device for media streams (e.g. WebRTC). Works with --use-fake-device-for-media-stream. 'use-fake-ui-for-media-stream', ]; + if (process.platform === 'linux') { + // The /dev/shm partition is too small in certain VM environments, causing + // Chrome to fail or crash. Use this flag to work-around this issue + // (a temporary directory will always be used to create anonymous shared memory files). + chromeOptions.push('disable-dev-shm-usage'); + } if (headlessBrowser === '1') { // Use --disable-gpu to avoid an error from a missing Mesa library, as per // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 68641c2fd6b1ff..766e6168002c29 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.8.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx index 5d464cf0405d04..7c1406f5b20c3a 100644 --- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx +++ b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx @@ -76,7 +76,7 @@ const PageA = () => ( - Page A's content goes here + Page A's content goes here ); diff --git a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts index 67c9120b1f2d92..771e50b22f66ba 100644 --- a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts @@ -29,7 +29,7 @@ declare module 'kibana/server' { export class CorePluginBPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { const router = core.http.createRouter(); - router.get({ path: '/core_plugin_b/', validate: false }, async (context, req, res) => { + router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => { if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' }); const response = await context.pluginA.ping(); return res.ok({ body: `Pong via plugin A: ${response}` }); diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index a03849129192f5..7c5b6f6be58af2 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.8.0", "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 18b14516b9be41..ef472b4026957e 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.8.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index b147da6a448ae5..277bb09ac745c5 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.8.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/index.js b/test/plugin_functional/plugins/kbn_tp_top_nav/index.js new file mode 100644 index 00000000000000..144050beb78689 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/index.js @@ -0,0 +1,30 @@ +/* + * 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 default function (kibana) { + return new kibana.Plugin({ + uiExports: { + app: { + title: 'Top Nav Menu test', + description: 'This is a sample plugin for the functional tests.', + main: 'plugins/kbn_tp_top_nav/app', + } + } + }); +} diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/package.json b/test/plugin_functional/plugins/kbn_tp_top_nav/package.json new file mode 100644 index 00000000000000..7102d24d3292de --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/package.json @@ -0,0 +1,9 @@ +{ + "name": "kbn_tp_top_nav", + "version": "1.0.0", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0" +} diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js b/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js new file mode 100644 index 00000000000000..e7f97e68c086d3 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js @@ -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 React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { uiModules } from 'ui/modules'; +import chrome from 'ui/chrome'; + +// This is required so some default styles and required scripts/Angular modules are loaded, +// or the timezone setting is correctly applied. +import 'ui/autoload/all'; + +import { AppWithTopNav } from './top_nav'; + +const app = uiModules.get('apps/topnavDemoPlugin', ['kibana']); + +app.config($locationProvider => { + $locationProvider.html5Mode({ + enabled: false, + requireBase: false, + rewriteLinks: false, + }); +}); + +function RootController($scope, $element) { + const domNode = $element[0]; + + // render react to DOM + render(, domNode); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + unmountComponentAtNode(domNode); + }); +} + +chrome.setRootController('topnavDemoPlugin', RootController); diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx new file mode 100644 index 00000000000000..8678ab705df9c4 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx @@ -0,0 +1,53 @@ +/* + * 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 { + setup as navSetup, + start as navStart, +} from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; + +const customExtension = { + id: 'registered-prop', + label: 'Registered Button', + description: 'Registered Demo', + run() {}, + testId: 'demoRegisteredNewButton', +}; + +navSetup.registerMenuItem(customExtension); + +export const AppWithTopNav = () => { + const { TopNavMenu } = navStart.ui; + const config = [ + { + id: 'new', + label: 'New Button', + description: 'New Demo', + run() {}, + testId: 'demoNewButton', + }, + ]; + + return ( + + Hey + + ); +}; diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json new file mode 100644 index 00000000000000..1ba21f11b7de2a --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index 10f122823353ce..f248a7e4d1f2d4 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.8.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx b/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx index 665b3c9f0ae036..8a0dd31e3595f2 100644 --- a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx +++ b/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx @@ -127,7 +127,7 @@ export class DemoStrategy extends React.Component { }, ]} demo={this.renderDemo()} - > + /> ); } diff --git a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx b/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx index 20de631bc56052..d35c67191a1f80 100644 --- a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx +++ b/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx @@ -139,7 +139,7 @@ export class EsSearchTest extends React.Component { }, ]} demo={this.renderDemo()} - > + /> ); } diff --git a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx b/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx index fe67e4097b2a3e..1562e33b14c2f5 100644 --- a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx +++ b/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx @@ -110,7 +110,7 @@ export class GuideSection extends React.Component { return code.map((codeBlock, i) => ( - +

{codeBlock.description}

{this.removeLicenseBlock(codeBlock.snippet)} diff --git a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx b/test/plugin_functional/plugins/search_explorer/public/search_api.tsx index 3e4768c8700643..8ec6225d1f172d 100644 --- a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx +++ b/test/plugin_functional/plugins/search_explorer/public/search_api.tsx @@ -83,5 +83,5 @@ export const SearchApiPage = () => ( ], }, ]} - > + /> ); diff --git a/test/plugin_functional/plugins/ui_settings_plugin/kibana.json b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json new file mode 100644 index 00000000000000..05d2dca0af9377 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "ui_settings_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["ui_settings_plugin"], + "server": true, + "ui": true +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/package.json b/test/plugin_functional/plugins/ui_settings_plugin/package.json new file mode 100644 index 00000000000000..6a0d5999412ff0 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "ui_settings_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/ui_settings_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts b/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts new file mode 100644 index 00000000000000..3c5997132d460d --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/public/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. + */ +import { UiSettingsPlugin } from './plugin'; + +export const plugin = () => new UiSettingsPlugin(); diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx b/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx new file mode 100644 index 00000000000000..883d203b4c37ac --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx @@ -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 { CoreSetup, Plugin } from 'kibana/public'; + +declare global { + interface Window { + uiSettingsPlugin?: Record; + uiSettingsPluginValue?: string; + } +} + +export class UiSettingsPlugin implements Plugin { + public setup(core: CoreSetup) { + window.uiSettingsPlugin = core.uiSettings.getAll().ui_settings_plugin; + window.uiSettingsPluginValue = core.uiSettings.get('ui_settings_plugin'); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/server/index.ts b/test/plugin_functional/plugins/ui_settings_plugin/server/index.ts new file mode 100644 index 00000000000000..7715cef31d72c3 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/server/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { UiSettingsPlugin } from './plugin'; + +export const plugin = () => new UiSettingsPlugin(); diff --git a/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts b/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts new file mode 100644 index 00000000000000..c32e8a75d95da5 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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 { Plugin, CoreSetup } from 'kibana/server'; + +export class UiSettingsPlugin implements Plugin { + public setup(core: CoreSetup) { + core.uiSettings.register({ + ui_settings_plugin: { + name: 'from_ui_settings_plugin', + description: 'just for testing', + value: '2', + category: ['any'], + }, + }); + + const router = core.http.createRouter(); + router.get({ path: '/api/ui-settings-plugin', validate: false }, async (context, req, res) => { + const uiSettingsValue = await context.core.uiSettings.client.get( + 'ui_settings_plugin' + ); + return res.ok({ body: { uiSettingsValue } }); + }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json new file mode 100644 index 00000000000000..1ba21f11b7de2a --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/services/index.js b/test/plugin_functional/services/index.js deleted file mode 100644 index bf02587772f4b5..00000000000000 --- a/test/plugin_functional/services/index.js +++ /dev/null @@ -1,24 +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 { KibanaSupertestProvider } from './supertest'; - -export const services = { - supertest: KibanaSupertestProvider, -}; diff --git a/test/plugin_functional/services/index.ts b/test/plugin_functional/services/index.ts new file mode 100644 index 00000000000000..dd2b25e14fe170 --- /dev/null +++ b/test/plugin_functional/services/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { FtrProviderContext } from 'test/functional/ftr_provider_context'; + +import { KibanaSupertestProvider } from './supertest'; + +export const services = { + supertest: KibanaSupertestProvider, +}; + +export type PluginFunctionalProviderContext = FtrProviderContext & + GenericFtrProviderContext; diff --git a/test/plugin_functional/services/supertest.js b/test/plugin_functional/services/supertest.js deleted file mode 100644 index 390f89acaa7758..00000000000000 --- a/test/plugin_functional/services/supertest.js +++ /dev/null @@ -1,29 +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 { format as formatUrl } from 'url'; - -import supertestAsPromised from 'supertest-as-promised'; - -export function KibanaSupertestProvider({ getService }) { - const config = getService('config'); - const kibanaServerUrl = formatUrl(config.get('servers.kibana')); - return supertestAsPromised(kibanaServerUrl); -} diff --git a/test/plugin_functional/services/supertest.ts b/test/plugin_functional/services/supertest.ts new file mode 100644 index 00000000000000..6b7dc26248c061 --- /dev/null +++ b/test/plugin_functional/services/supertest.ts @@ -0,0 +1,28 @@ +/* + * 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 { format as formatUrl } from 'url'; +import { FtrProviderContext } from 'test/functional/ftr_provider_context'; + +import supertestAsPromised from 'supertest-as-promised'; + +export function KibanaSupertestProvider({ getService }: FtrProviderContext) { + const config = getService('config'); + const kibanaServerUrl = formatUrl(config.get('servers.kibana')); + return supertestAsPromised(kibanaServerUrl); +} diff --git a/test/plugin_functional/test_suites/core_plugins/applications.js b/test/plugin_functional/test_suites/core_plugins/applications.js deleted file mode 100644 index 4c4c198d1af94a..00000000000000 --- a/test/plugin_functional/test_suites/core_plugins/applications.js +++ /dev/null @@ -1,104 +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 url from 'url'; -import expect from '@kbn/expect'; - -export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common']); - - const browser = getService('browser'); - const appsMenu = getService('appsMenu'); - const testSubjects = getService('testSubjects'); - - const loadingScreenNotShown = async () => - expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); - - const loadingScreenShown = () => - testSubjects.existOrFail('kbnLoadingMessage'); - - const getKibanaUrl = (pathname, search) => url.format({ - protocol: 'http:', - hostname: process.env.TEST_KIBANA_HOST || 'localhost', - port: process.env.TEST_KIBANA_PORT || '5620', - pathname, - search, - }); - - describe('ui applications', function describeIndexTests() { - before(async () => { - await PageObjects.common.navigateToApp('foo'); - }); - - it('starts on home page', async () => { - await testSubjects.existOrFail('fooAppHome'); - }); - - it('navigates to its own pages', async () => { - // Go to page A - await testSubjects.click('fooNavPageA'); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/page-a')); - await loadingScreenNotShown(); - await testSubjects.existOrFail('fooAppPageA'); - - // Go to home page - await testSubjects.click('fooNavHome'); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/')); - await loadingScreenNotShown(); - await testSubjects.existOrFail('fooAppHome'); - }); - - it('can use the back button to navigate within an app', async () => { - await browser.goBack(); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/page-a')); - await loadingScreenNotShown(); - await testSubjects.existOrFail('fooAppPageA'); - }); - - it('navigates to other apps', async () => { - await testSubjects.click('fooNavBarPageB'); - await loadingScreenNotShown(); - await testSubjects.existOrFail('barAppPageB'); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/bar/page-b', 'query=here')); - }); - - it('preserves query parameters across apps', async () => { - const querySpan = await testSubjects.find('barAppPageBQuery'); - expect(await querySpan.getVisibleText()).to.eql(`[["query","here"]]`); - }); - - it('can use the back button to navigate back to previous app', async () => { - await browser.goBack(); - expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/page-a')); - await loadingScreenNotShown(); - await testSubjects.existOrFail('fooAppPageA'); - }); - - it('can navigate from NP apps to legacy apps', async () => { - await appsMenu.clickLink('Management'); - await loadingScreenShown(); - await testSubjects.existOrFail('managementNav'); - }); - - it('can navigate from legacy apps to NP apps', async () => { - await appsMenu.clickLink('Foo'); - await loadingScreenShown(); - await testSubjects.existOrFail('fooAppHome'); - }); - }); -} diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts new file mode 100644 index 00000000000000..eec2ec019a5150 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -0,0 +1,106 @@ +/* + * 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 url from 'url'; +import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + + const browser = getService('browser'); + const appsMenu = getService('appsMenu'); + const testSubjects = getService('testSubjects'); + + const loadingScreenNotShown = async () => + expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); + + const loadingScreenShown = () => testSubjects.existOrFail('kbnLoadingMessage'); + + const getKibanaUrl = (pathname?: string, search?: string) => + url.format({ + protocol: 'http:', + hostname: process.env.TEST_KIBANA_HOST || 'localhost', + port: process.env.TEST_KIBANA_PORT || '5620', + pathname, + search, + }); + + describe('ui applications', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToApp('foo'); + }); + + it('starts on home page', async () => { + await testSubjects.existOrFail('fooAppHome'); + }); + + it('navigates to its own pages', async () => { + // Go to page A + await testSubjects.click('fooNavPageA'); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/page-a')); + await loadingScreenNotShown(); + await testSubjects.existOrFail('fooAppPageA'); + + // Go to home page + await testSubjects.click('fooNavHome'); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/')); + await loadingScreenNotShown(); + await testSubjects.existOrFail('fooAppHome'); + }); + + it('can use the back button to navigate within an app', async () => { + await browser.goBack(); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/page-a')); + await loadingScreenNotShown(); + await testSubjects.existOrFail('fooAppPageA'); + }); + + it('navigates to other apps', async () => { + await testSubjects.click('fooNavBarPageB'); + await loadingScreenNotShown(); + await testSubjects.existOrFail('barAppPageB'); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/bar/page-b', 'query=here')); + }); + + it('preserves query parameters across apps', async () => { + const querySpan = await testSubjects.find('barAppPageBQuery'); + expect(await querySpan.getVisibleText()).to.eql(`[["query","here"]]`); + }); + + it('can use the back button to navigate back to previous app', async () => { + await browser.goBack(); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/foo/page-a')); + await loadingScreenNotShown(); + await testSubjects.existOrFail('fooAppPageA'); + }); + + it('can navigate from NP apps to legacy apps', async () => { + await appsMenu.clickLink('Management'); + await loadingScreenShown(); + await testSubjects.existOrFail('managementNav'); + }); + + it('can navigate from legacy apps to NP apps', async () => { + await appsMenu.clickLink('Foo'); + await loadingScreenShown(); + await testSubjects.existOrFail('fooAppHome'); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/index.js b/test/plugin_functional/test_suites/core_plugins/index.js deleted file mode 100644 index eeb81f67751edd..00000000000000 --- a/test/plugin_functional/test_suites/core_plugins/index.js +++ /dev/null @@ -1,27 +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. - */ - -export default function ({ loadTestFile }) { - describe('core plugins', () => { - loadTestFile(require.resolve('./applications')); - loadTestFile(require.resolve('./legacy_plugins')); - loadTestFile(require.resolve('./server_plugins')); - loadTestFile(require.resolve('./ui_plugins')); - }); -} diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts new file mode 100644 index 00000000000000..bf33f37694c3a7 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -0,0 +1,31 @@ +/* + * 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 { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ loadTestFile }: PluginFunctionalProviderContext) { + describe('core plugins', () => { + loadTestFile(require.resolve('./applications')); + loadTestFile(require.resolve('./legacy_plugins')); + loadTestFile(require.resolve('./server_plugins')); + loadTestFile(require.resolve('./ui_plugins')); + loadTestFile(require.resolve('./ui_settings')); + loadTestFile(require.resolve('./top_nav')); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js deleted file mode 100644 index c6edf803c9938a..00000000000000 --- a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js +++ /dev/null @@ -1,51 +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 expect from '@kbn/expect'; - -export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common']); - const testSubjects = getService('testSubjects'); - const supertest = getService('supertest'); - - describe('legacy plugins', function describeIndexTests() { - describe('http', () => { - it('has access to New Platform HTTP service', async () => { - await supertest - .get('/api/np-http-in-legacy') - .expect(200) - .expect('Pong in legacy via new platform: true'); - }); - - it('has access to New Platform HTTP context providers', async () => { - await supertest - .get('/api/np-context-in-legacy') - .expect(200) - .expect(JSON.stringify({ contexts: ['core', 'search', 'pluginA'] })); - }); - }); - - describe('application service compatibility layer', function describeIndexTests() { - it('can render legacy apps', async () => { - await PageObjects.common.navigateToApp('core_plugin_legacy'); - expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); - }); - }); - }); -} diff --git a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.ts b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.ts new file mode 100644 index 00000000000000..d0c01f1e9caf37 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.ts @@ -0,0 +1,53 @@ +/* + * 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 expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + const supertest = getService('supertest'); + + describe('legacy plugins', () => { + describe('http', () => { + it('has access to New Platform HTTP service', async () => { + await supertest + .get('/api/np-http-in-legacy') + .expect(200) + .expect('Pong in legacy via new platform: true'); + }); + + it('has access to New Platform HTTP context providers', async () => { + await supertest + .get('/api/np-context-in-legacy') + .expect(200) + .expect(JSON.stringify({ contexts: ['core', 'search', 'pluginA'] })); + }); + }); + + describe('application service compatibility layer', () => { + it('can render legacy apps', async () => { + await PageObjects.common.navigateToApp('core_plugin_legacy'); + expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/server_plugins.js b/test/plugin_functional/test_suites/core_plugins/server_plugins.js deleted file mode 100644 index a17b19468d6a80..00000000000000 --- a/test/plugin_functional/test_suites/core_plugins/server_plugins.js +++ /dev/null @@ -1,35 +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 expect from '@kbn/expect'; - -export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common']); - const browser = getService('browser'); - - describe('server plugins', function describeIndexTests() { - it('extend request handler context', async () => { - const url = `${PageObjects.common.getHostPort()}/core_plugin_b/`; - await browser.get(url); - - const pageSource = await browser.execute('return window.document.body.textContent;'); - expect(pageSource).to.equal('Pong via plugin A: true'); - }); - }); -} diff --git a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts new file mode 100644 index 00000000000000..3881af56429963 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/server_plugins.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 { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + + describe('server plugins', function describeIndexTests() { + it('extend request handler context', async () => { + await supertest + .get('/core_plugin_b') + .expect(200) + .expect('Pong via plugin A: true'); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/top_nav.js b/test/plugin_functional/test_suites/core_plugins/top_nav.js new file mode 100644 index 00000000000000..5c46e3d7f76db1 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/top_nav.js @@ -0,0 +1,40 @@ +/* + * 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 expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['common']); + + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + + describe.skip('top nav', function describeIndexTests() { + before(async () => { + const url = `${PageObjects.common.getHostPort()}/app/kbn_tp_top_nav/`; + await browser.get(url); + }); + + it('Shows registered menu items', async () => { + const ownMenuItem = await testSubjects.find('demoNewButton'); + expect(await ownMenuItem.getVisibleText()).to.be('New Button'); + const demoRegisteredNewButton = await testSubjects.find('demoRegisteredNewButton'); + expect(await demoRegisteredNewButton.getVisibleText()).to.be('Registered Button'); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.js b/test/plugin_functional/test_suites/core_plugins/ui_plugins.js deleted file mode 100644 index 15a4dcabddbd1a..00000000000000 --- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.js +++ /dev/null @@ -1,59 +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 expect from '@kbn/expect'; - -export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common']); - const browser = getService('browser'); - - describe('ui plugins', function () { - describe('loading', function describeIndexTests() { - before(async () => { - await PageObjects.common.navigateToApp('settings'); - }); - - it('should attach string to window.corePluginB', async () => { - const corePluginB = await browser.execute('return window.corePluginB'); - expect(corePluginB).to.equal(`Plugin A said: Hello from Plugin A!`); - }); - }); - describe('have injectedMetadata service provided', function describeIndexTests() { - before(async () => { - await PageObjects.common.navigateToApp('bar'); - }); - - it('should attach string to window.corePluginB', async () => { - const hasAccessToInjectedMetadata = await browser.execute('return window.hasAccessToInjectedMetadata'); - expect(hasAccessToInjectedMetadata).to.equal(true); - }); - }); - describe('have env data provided', function describeIndexTests() { - before(async () => { - await PageObjects.common.navigateToApp('bar'); - }); - - it('should attach pluginContext to window.corePluginB', async () => { - const envData = await browser.execute('return window.env'); - expect(envData.mode.dev).to.be(true); - expect(envData.packageInfo.version).to.be.a('string'); - }); - }); - }); -} diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts new file mode 100644 index 00000000000000..a971921ad3ed83 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts @@ -0,0 +1,63 @@ +/* + * 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 expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + + describe('ui plugins', function() { + describe('loading', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToApp('settings'); + }); + + it('should attach string to window.corePluginB', async () => { + const corePluginB = await browser.execute('return window.corePluginB'); + expect(corePluginB).to.equal(`Plugin A said: Hello from Plugin A!`); + }); + }); + describe('have injectedMetadata service provided', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToApp('bar'); + }); + + it('should attach string to window.corePluginB', async () => { + const hasAccessToInjectedMetadata = await browser.execute( + 'return window.hasAccessToInjectedMetadata' + ); + expect(hasAccessToInjectedMetadata).to.equal(true); + }); + }); + describe('have env data provided', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToApp('bar'); + }); + + it('should attach pluginContext to window.corePluginB', async () => { + const envData: any = await browser.execute('return window.env'); + expect(envData.mode.dev).to.be(true); + expect(envData.packageInfo.version).to.be.a('string'); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/ui_settings.ts b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts new file mode 100644 index 00000000000000..2b4227ee798e3f --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts @@ -0,0 +1,52 @@ +/* + * 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 expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const supertest = getService('supertest'); + + describe('ui settings', function() { + before(async () => { + await PageObjects.common.navigateToApp('settings'); + }); + + it('client plugins have access to registered settings', async () => { + const settings = await browser.execute('return window.uiSettingsPlugin'); + expect(settings).to.eql({ + category: ['any'], + description: 'just for testing', + name: 'from_ui_settings_plugin', + value: '2', + }); + const settingsValue = await browser.execute('return window.uiSettingsPluginValue'); + expect(settingsValue).to.be('2'); + }); + + it('server plugins have access to registered settings', async () => { + await supertest + .get('/api/ui-settings-plugin') + .expect(200) + .expect({ uiSettingsValue: 2 }); + }); + }); +} diff --git a/test/scripts/jenkins_accessibility.sh b/test/scripts/jenkins_accessibility.sh new file mode 100755 index 00000000000000..0b3d8dc3f85c23 --- /dev/null +++ b/test/scripts/jenkins_accessibility.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e + +if [[ -n "$IS_PIPELINE_JOB" ]] ; then + source src/dev/ci_setup/setup_env.sh +fi + +export TEST_BROWSER_HEADLESS=1 + +if [[ -z "$IS_PIPELINE_JOB" ]] ; then + yarn run grunt functionalTests:ensureAllTestsInCiGroup; + node scripts/build --debug --oss; +else + installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" + destDir=${installDir}-${CI_WORKER_NUMBER} + cp -R "$installDir" "$destDir" + + export KIBANA_INSTALL_DIR="$destDir" +fi + +checks-reporter-with-killswitch "Kibana accessibility tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$installDir" \ + --config test/accessibility/config.ts; diff --git a/test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh b/test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh new file mode 100755 index 00000000000000..4b16e3b32fefd1 --- /dev/null +++ b/test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd test/plugin_functional/plugins/kbn_tp_sample_panel_action; +if [[ ! -d "target" ]]; then + checks-reporter-with-killswitch "Build kbn_tp_sample_panel_action" yarn build; +fi +cd -; diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 6deddd5a59152f..c6bddb69aa5709 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -1,12 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - -export TEST_BROWSER_HEADLESS=1 +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then yarn run grunt functionalTests:ensureAllTestsInCiGroup; @@ -22,10 +16,7 @@ fi checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; if [ "$CI_GROUP" == "1" ]; then - # build kbn_tp_sample_panel_action - cd test/plugin_functional/plugins/kbn_tp_sample_panel_action; - checks-reporter-with-killswitch "Build kbn_tp_sample_panel_action" yarn build; - cd -; + source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh yarn run grunt run:pluginFunctionalTestsRelease --from=source; yarn run grunt run:interpreterFunctionalTestsRelease; fi diff --git a/test/scripts/jenkins_firefox_smoke.sh b/test/scripts/jenkins_firefox_smoke.sh index 69cedc9657340b..9a31f5f43d2242 100755 --- a/test/scripts/jenkins_firefox_smoke.sh +++ b/test/scripts/jenkins_firefox_smoke.sh @@ -1,10 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then node scripts/build --debug --oss; diff --git a/test/scripts/jenkins_test_setup.sh b/test/scripts/jenkins_test_setup.sh new file mode 100644 index 00000000000000..e2dd0bc276bb64 --- /dev/null +++ b/test/scripts/jenkins_test_setup.sh @@ -0,0 +1,20 @@ +set -e + +function post_work() { + set +e + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + node "$KIBANA_DIR/scripts/report_failed_tests" + fi + + if [[ -z "$REMOVE_KIBANA_INSTALL_DIR" && -z "$KIBANA_INSTALL_DIR" && -d "$KIBANA_INSTALL_DIR" ]]; then + rm -rf "$REMOVE_KIBANA_INSTALL_DIR" + fi +} + +trap 'post_work' EXIT + +export TEST_BROWSER_HEADLESS=1 + +if [[ -n "$IS_PIPELINE_JOB" ]] ; then + source src/dev/ci_setup/setup_env.sh +fi diff --git a/test/scripts/jenkins_visual_regression.sh b/test/scripts/jenkins_visual_regression.sh index fdda24423d977a..9ca1c0f08d2c94 100755 --- a/test/scripts/jenkins_visual_regression.sh +++ b/test/scripts/jenkins_visual_regression.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - +source test/scripts/jenkins_test_setup.sh source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" if [[ -z "$IS_PIPELINE_JOB" ]] ; then @@ -22,8 +17,6 @@ else export KIBANA_INSTALL_DIR="$destDir" fi -export TEST_BROWSER_HEADLESS=1 - checks-reporter-with-killswitch "Kibana visual regression tests" \ yarn run percy exec -t 500 \ node scripts/functional_tests \ diff --git a/test/scripts/jenkins_xpack_accessibility.sh b/test/scripts/jenkins_xpack_accessibility.sh new file mode 100755 index 00000000000000..af813c3c40f841 --- /dev/null +++ b/test/scripts/jenkins_xpack_accessibility.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e + +if [[ -n "$IS_PIPELINE_JOB" ]] ; then + source src/dev/ci_setup/setup_env.sh +fi + +if [[ -z "$IS_PIPELINE_JOB" ]] ; then + echo " -> building and extracting default Kibana distributable for use in functional tests" + node scripts/build --debug --no-oss + + linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" + installDir="$PARENT_DIR/install/kibana" + + mkdir -p "$installDir" + tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + + export KIBANA_INSTALL_DIR="$installDir" +else + installDir="$PARENT_DIR/install/kibana" + destDir="${installDir}-${CI_WORKER_NUMBER}" + cp -R "$installDir" "$destDir" + + export KIBANA_INSTALL_DIR="$destDir" +fi + +export TEST_BROWSER_HEADLESS=1 +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "X-Pack accessibility tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$installDir" \ + --config test/accessibility/config.ts; diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 0b9666b33decab..fba05f8f252d76 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -1,12 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - -export TEST_BROWSER_HEADLESS=1 +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then echo " -> Ensuring all functional tests are in a ciGroup" diff --git a/test/scripts/jenkins_xpack_firefox_smoke.sh b/test/scripts/jenkins_xpack_firefox_smoke.sh index 18ed468de1317a..43220459bcb97d 100755 --- a/test/scripts/jenkins_xpack_firefox_smoke.sh +++ b/test/scripts/jenkins_xpack_firefox_smoke.sh @@ -1,10 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then node scripts/build --debug --no-oss; @@ -21,8 +17,6 @@ else export KIBANA_INSTALL_DIR="$destDir" fi -export TEST_BROWSER_HEADLESS=1 - cd "$XPACK_DIR" checks-reporter-with-killswitch "X-Pack firefox smoke test" \ diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh index 0f3275d45f13b2..5699f9e5ee7c14 100755 --- a/test/scripts/jenkins_xpack_visual_regression.sh +++ b/test/scripts/jenkins_xpack_visual_regression.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - +source test/scripts/jenkins_test_setup.sh source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" if [[ -z "$IS_PIPELINE_JOB" ]] ; then @@ -23,8 +18,6 @@ else export KIBANA_INSTALL_DIR="$destDir" fi -export TEST_BROWSER_HEADLESS=1 - cd "$XPACK_DIR" checks-reporter-with-killswitch "X-Pack visual regression tests" \ diff --git a/test/visual_regression/tests/discover/chart_visualization.js b/test/visual_regression/tests/discover/chart_visualization.js index 5467f2d3eaa145..4e1d9bf643cf6d 100644 --- a/test/visual_regression/tests/discover/chart_visualization.js +++ b/test/visual_regression/tests/discover/chart_visualization.js @@ -29,6 +29,7 @@ export default function ({ getService, getPageObjects }) { const visualTesting = getService('visualTesting'); const defaultSettings = { defaultIndex: 'logstash-*', + 'discover:sampleSize': 1 }; describe('discover', function describeIndexTests() { diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy new file mode 100644 index 00000000000000..e8d7cc03edad0a --- /dev/null +++ b/vars/kibanaPipeline.groovy @@ -0,0 +1,251 @@ +def withWorkers(name, preWorkerClosure = {}, workerClosures = [:]) { + return { + jobRunner('tests-xl', true) { + try { + doSetup() + preWorkerClosure() + + def nextWorker = 1 + def worker = { workerClosure -> + def workerNumber = nextWorker + nextWorker++ + + return { + workerClosure(workerNumber) + } + } + + def workers = [:] + workerClosures.each { workerName, workerClosure -> + workers[workerName] = worker(workerClosure) + } + + parallel(workers) + } finally { + catchError { + uploadAllGcsArtifacts(name) + } + + catchError { + runbld.junit() + } + + catchError { + publishJunit() + } + + catchError { + runErrorReporter() + } + } + } + } +} + +def getPostBuildWorker(name, closure) { + return { workerNumber -> + def kibanaPort = "61${workerNumber}1" + def esPort = "61${workerNumber}2" + def esTransportPort = "61${workerNumber}3" + + withEnv([ + "CI_WORKER_NUMBER=${workerNumber}", + "TEST_KIBANA_HOST=localhost", + "TEST_KIBANA_PORT=${kibanaPort}", + "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", + "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", + "TEST_ES_TRANSPORT_PORT=${esTransportPort}", + "IS_PIPELINE_JOB=1", + ]) { + closure() + } + } +} + +def getOssCiGroupWorker(ciGroup) { + return getPostBuildWorker("ciGroup" + ciGroup, { + withEnv([ + "CI_GROUP=${ciGroup}", + "JOB=kibana-ciGroup${ciGroup}", + ]) { + runbld "./test/scripts/jenkins_ci_group.sh" + } + }) +} + +def getXpackCiGroupWorker(ciGroup) { + return getPostBuildWorker("xpack-ciGroup" + ciGroup, { + withEnv([ + "CI_GROUP=${ciGroup}", + "JOB=xpack-kibana-ciGroup${ciGroup}", + ]) { + runbld "./test/scripts/jenkins_xpack_ci_group.sh" + } + }) +} + +def legacyJobRunner(name) { + return { + parallel([ + "${name}": { + withEnv([ + "JOB=${name}", + ]) { + jobRunner('linux && immutable', false) { + try { + runbld('.ci/run.sh', true) + } finally { + catchError { + uploadAllGcsArtifacts(name) + } + catchError { + publishJunit() + } + catchError { + runErrorReporter() + } + } + } + } + } + ]) + } +} + +def jobRunner(label, useRamDisk, closure) { + node(label) { + if (useRamDisk) { + // Move to a temporary workspace, so that we can symlink the real workspace into /dev/shm + def originalWorkspace = env.WORKSPACE + ws('/tmp/workspace') { + sh """ + mkdir -p /dev/shm/workspace + mkdir -p '${originalWorkspace}' # create all of the directories leading up to the workspace, if they don't exist + rm --preserve-root -rf '${originalWorkspace}' # then remove just the workspace, just in case there's stuff in it + ln -s /dev/shm/workspace '${originalWorkspace}' + """ + } + } + + def scmVars = checkout scm + + withEnv([ + "CI=true", + "HOME=${env.JENKINS_HOME}", + "PR_SOURCE_BRANCH=${env.ghprbSourceBranch ?: ''}", + "PR_TARGET_BRANCH=${env.ghprbTargetBranch ?: ''}", + "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", + "TEST_BROWSER_HEADLESS=1", + "GIT_BRANCH=${scmVars.GIT_BRANCH}", + ]) { + withCredentials([ + string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), + string(credentialsId: 'vault-role-id', variable: 'VAULT_ROLE_ID'), + string(credentialsId: 'vault-secret-id', variable: 'VAULT_SECRET_ID'), + ]) { + // scm is configured to check out to the ./kibana directory + dir('kibana') { + closure() + } + } + } + } +} + +// TODO what should happen if GCS, Junit, or email publishing fails? Unstable build? Failed build? + +def uploadGcsArtifact(workerName, pattern) { + def storageLocation = "gs://kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" // TODO + + googleStorageUpload( + credentialsId: 'kibana-ci-gcs-plugin', + bucket: storageLocation, + pattern: pattern, + sharedPublicly: true, + showInline: true, + ) +} + +def uploadAllGcsArtifacts(workerName) { + def ARTIFACT_PATTERNS = [ + 'target/kibana-*', + 'target/junit/**/*', + 'test/**/screenshots/**/*.png', + 'test/functional/failure_debug/html/*.html', + 'x-pack/test/**/screenshots/**/*.png', + 'x-pack/test/functional/failure_debug/html/*.html', + 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', + ] + + ARTIFACT_PATTERNS.each { pattern -> + uploadGcsArtifact(workerName, pattern) + } +} + +def publishJunit() { + junit(testResults: 'target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) +} + +def sendMail() { + // If the build doesn't have a result set by this point, there haven't been any errors and it can be marked as a success + // The e-mail plugin for the infra e-mail depends upon this being set + currentBuild.result = currentBuild.result ?: 'SUCCESS' + + def buildStatus = buildUtils.getBuildStatus() + if (buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { + node('flyweight') { + sendInfraMail() + sendKibanaMail() + } + } +} + +def sendInfraMail() { + catchError { + step([ + $class: 'Mailer', + notifyEveryUnstableBuild: true, + recipients: 'infra-root+build@elastic.co', + sendToIndividuals: false + ]) + } +} + +def sendKibanaMail() { + catchError { + def buildStatus = buildUtils.getBuildStatus() + if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { + emailext( + to: 'build-kibana@elastic.co', + subject: "${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - ${buildStatus}", + body: '${SCRIPT,template="groovy-html.template"}', + mimeType: 'text/html', + ) + } + } +} + +def bash(script) { + sh "#!/bin/bash\n${script}" +} + +def doSetup() { + runbld "./test/scripts/jenkins_setup.sh" +} + +def buildOss() { + runbld "./test/scripts/jenkins_build_kibana.sh" +} + +def buildXpack() { + runbld "./test/scripts/jenkins_xpack_build_kibana.sh" +} + +def runErrorReporter() { + bash """ + source src/dev/ci_setup/setup_env.sh + node scripts/report_failed_tests + """ +} + +return this diff --git a/vars/runbld.groovy b/vars/runbld.groovy new file mode 100644 index 00000000000000..501e2421ca65b5 --- /dev/null +++ b/vars/runbld.groovy @@ -0,0 +1,11 @@ +def call(script, enableJunitProcessing = false) { + def extraConfig = enableJunitProcessing ? "" : "--config ${env.WORKSPACE}/kibana/.ci/runbld_no_junit.yml" + + sh "/usr/local/bin/runbld -d '${pwd()}' ${extraConfig} ${script}" +} + +def junit() { + sh "/usr/local/bin/runbld -d '${pwd()}' ${env.WORKSPACE}/kibana/test/scripts/jenkins_runbld_junit.sh" +} + +return this diff --git a/webpackShims/lru-cache.js b/webpackShims/lru-cache.js new file mode 100644 index 00000000000000..9cc11a516378e0 --- /dev/null +++ b/webpackShims/lru-cache.js @@ -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. + */ + +module.exports = require('../node_modules/lru-cache'); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 735ee0b6b67b59..6d0da2f0b693d3 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -7,7 +7,6 @@ "xpack.apm": "legacy/plugins/apm", "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", - "xpack.code": ["legacy/plugins/code", "plugins/code"], "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.features": "plugins/features", @@ -31,10 +30,11 @@ "xpack.rollupJobs": "legacy/plugins/rollup", "xpack.searchProfiler": "legacy/plugins/searchprofiler", "xpack.siem": "legacy/plugins/siem", - "xpack.security": "legacy/plugins/security", + "xpack.security": ["legacy/plugins/security", "plugins/security"], "xpack.server": "legacy/server", "xpack.snapshotRestore": "legacy/plugins/snapshot_restore", "xpack.spaces": ["legacy/plugins/spaces", "plugins/spaces"], + "xpack.taskManager": "legacy/plugins/task_manager", "xpack.transform": "legacy/plugins/transform", "xpack.upgradeAssistant": "legacy/plugins/upgrade_assistant", "xpack.uptime": "legacy/plugins/uptime", diff --git a/x-pack/dev-tools/jest/setup/polyfills.js b/x-pack/dev-tools/jest/setup/polyfills.js index 8e5c5a8025b823..566e4701eeaacc 100644 --- a/x-pack/dev-tools/jest/setup/polyfills.js +++ b/x-pack/dev-tools/jest/setup/polyfills.js @@ -14,5 +14,6 @@ bluebird.Promise.setScheduler(function (fn) { global.setImmediate.call(global, f const MutationObserver = require('mutation-observer'); Object.defineProperty(window, 'MutationObserver', { value: MutationObserver }); +require('whatwg-fetch'); const URL = { createObjectURL: () => '' }; Object.defineProperty(window, 'URL', { value: URL }); diff --git a/x-pack/index.js b/x-pack/index.js index 756d9b4d3127a5..2b467d2525d9b4 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -18,7 +18,6 @@ import { dashboardMode } from './legacy/plugins/dashboard_mode'; import { logstash } from './legacy/plugins/logstash'; import { beats } from './legacy/plugins/beats_management'; import { apm } from './legacy/plugins/apm'; -import { code } from './legacy/plugins/code'; import { maps } from './legacy/plugins/maps'; import { licenseManagement } from './legacy/plugins/license_management'; import { cloud } from './legacy/plugins/cloud'; @@ -62,7 +61,6 @@ module.exports = function (kibana) { logstash(kibana), beats(kibana), apm(kibana), - code(kibana), maps(kibana), canvas(kibana), licenseManagement(kibana), diff --git a/x-pack/legacy/plugins/TEST_PLAN.md b/x-pack/legacy/plugins/TEST_PLAN.md deleted file mode 100644 index dfda2522f27973..00000000000000 --- a/x-pack/legacy/plugins/TEST_PLAN.md +++ /dev/null @@ -1,4 +0,0 @@ -# APM UI Test Plan - -Moved to: https://github.com/elastic/observability-dev/blob/master/docs/apm/apm-ui-test-plan.md - diff --git a/x-pack/legacy/plugins/actions/index.ts b/x-pack/legacy/plugins/actions/index.ts index 7c4dd9f73c11f4..a58c936c637492 100644 --- a/x-pack/legacy/plugins/actions/index.ts +++ b/x-pack/legacy/plugins/actions/index.ts @@ -22,10 +22,10 @@ export function actions(kibana: any) { return new kibana.Plugin({ id: 'actions', configPrefix: 'xpack.actions', - require: ['kibana', 'elasticsearch', 'task_manager', 'encrypted_saved_objects'], + require: ['kibana', 'elasticsearch', 'task_manager', 'encryptedSavedObjects'], isEnabled(config: Legacy.KibanaConfig) { return ( - config.get('xpack.encrypted_saved_objects.enabled') === true && + config.get('xpack.encryptedSavedObjects.enabled') === true && config.get('xpack.actions.enabled') === true && config.get('xpack.task_manager.enabled') === true ); diff --git a/x-pack/legacy/plugins/actions/server/actions_client.test.ts b/x-pack/legacy/plugins/actions/server/actions_client.test.ts index b582d9f2a1b0dc..933b7b0b239b68 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.test.ts +++ b/x-pack/legacy/plugins/actions/server/actions_client.test.ts @@ -11,9 +11,14 @@ import { ActionsClient } from './actions_client'; import { ExecutorType } from './types'; import { ActionExecutor, TaskRunnerFactory } from './lib'; import { taskManagerMock } from '../../task_manager/task_manager.mock'; -import { SavedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, +} from '../../../../../src/core/server/mocks'; -const savedObjectsClient = SavedObjectsClientMock.create(); +const defaultKibanaIndex = '.kibana'; +const savedObjectsClient = savedObjectsClientMock.create(); +const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); const mockTaskManager = taskManagerMock.create(); @@ -22,11 +27,22 @@ const actionTypeRegistryParams = { taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()), }; +let actionsClient: ActionsClient; +let actionTypeRegistry: ActionTypeRegistry; const executor: ExecutorType = async options => { return { status: 'ok' }; }; -beforeEach(() => jest.resetAllMocks()); +beforeEach(() => { + jest.resetAllMocks(); + actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); + actionsClient = new ActionsClient({ + actionTypeRegistry, + savedObjectsClient, + scopedClusterClient, + defaultKibanaIndex, + }); +}); describe('create()', () => { test('creates an action with all given properties', async () => { @@ -40,16 +56,11 @@ describe('create()', () => { }, references: [], }; - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); actionTypeRegistry.register({ id: 'my-action-type', name: 'My action type', executor, }); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult); const result = await actionsClient.create({ action: { @@ -80,11 +91,6 @@ describe('create()', () => { }); test('validates config', async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); actionTypeRegistry.register({ id: 'my-action-type', name: 'My action type', @@ -110,11 +116,6 @@ describe('create()', () => { }); test(`throws an error when an action type doesn't exist`, async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); await expect( actionsClient.create({ action: { @@ -130,16 +131,11 @@ describe('create()', () => { }); test('encrypts action type options unless specified not to', async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); actionTypeRegistry.register({ id: 'my-action-type', name: 'My action type', executor, }); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'type', @@ -198,11 +194,6 @@ describe('create()', () => { describe('get()', () => { test('calls savedObjectsClient with id', async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.get.mockResolvedValueOnce({ id: '1', type: 'type', @@ -242,12 +233,12 @@ describe('find()', () => { }, ], }; - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.find.mockResolvedValueOnce(expectedResult); + scopedClusterClient.callAsInternalUser.mockResolvedValueOnce({ + aggregations: { + '1': { doc_count: 6 }, + }, + }); const result = await actionsClient.find({}); expect(result).toEqual({ total: 1, @@ -259,6 +250,7 @@ describe('find()', () => { config: { foo: 'bar', }, + referencedByCount: 6, }, ], }); @@ -276,11 +268,6 @@ describe('find()', () => { describe('delete()', () => { test('calls savedObjectsClient with id', async () => { const expectedResult = Symbol(); - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.delete.mockResolvedValueOnce(expectedResult); const result = await actionsClient.delete({ id: '1' }); expect(result).toEqual(expectedResult); @@ -296,16 +283,11 @@ describe('delete()', () => { describe('update()', () => { test('updates an action with all given properties', async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); actionTypeRegistry.register({ id: 'my-action-type', name: 'My action type', executor, }); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.get.mockResolvedValueOnce({ id: '1', type: 'action', @@ -362,11 +344,6 @@ describe('update()', () => { }); test('validates config', async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); actionTypeRegistry.register({ id: 'my-action-type', name: 'My action type', @@ -400,16 +377,11 @@ describe('update()', () => { }); test('encrypts action type options unless specified not to', async () => { - const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); actionTypeRegistry.register({ id: 'my-action-type', name: 'My action type', executor, }); - const actionsClient = new ActionsClient({ - actionTypeRegistry, - savedObjectsClient, - }); savedObjectsClient.get.mockResolvedValueOnce({ id: 'my-action', type: 'action', diff --git a/x-pack/legacy/plugins/actions/server/actions_client.ts b/x-pack/legacy/plugins/actions/server/actions_client.ts index 823cb55abaf5e7..1e4135bd0d66fd 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.ts +++ b/x-pack/legacy/plugins/actions/server/actions_client.ts @@ -4,10 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, SavedObjectAttributes, SavedObject } from 'src/core/server'; +import { + IScopedClusterClient, + SavedObjectsClientContract, + SavedObjectAttributes, + SavedObject, +} from 'src/core/server'; + import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; -import { ActionResult } from './types'; +import { ActionResult, FindActionResult, RawAction } from './types'; interface ActionUpdate extends SavedObjectAttributes { description: string; @@ -44,10 +50,12 @@ interface FindResult { page: number; perPage: number; total: number; - data: ActionResult[]; + data: FindActionResult[]; } interface ConstructorOptions { + defaultKibanaIndex: string; + scopedClusterClient: IScopedClusterClient; actionTypeRegistry: ActionTypeRegistry; savedObjectsClient: SavedObjectsClientContract; } @@ -58,12 +66,21 @@ interface UpdateOptions { } export class ActionsClient { + private readonly defaultKibanaIndex: string; + private readonly scopedClusterClient: IScopedClusterClient; private readonly savedObjectsClient: SavedObjectsClientContract; private readonly actionTypeRegistry: ActionTypeRegistry; - constructor({ actionTypeRegistry, savedObjectsClient }: ConstructorOptions) { + constructor({ + actionTypeRegistry, + defaultKibanaIndex, + scopedClusterClient, + savedObjectsClient, + }: ConstructorOptions) { this.actionTypeRegistry = actionTypeRegistry; this.savedObjectsClient = savedObjectsClient; + this.scopedClusterClient = scopedClusterClient; + this.defaultKibanaIndex = defaultKibanaIndex; } /** @@ -134,16 +151,22 @@ export class ActionsClient { * Find actions */ public async find({ options = {} }: FindOptions): Promise { - const findResult = await this.savedObjectsClient.find({ + const findResult = await this.savedObjectsClient.find({ ...options, type: 'action', }); + const data = await injectExtraFindData( + this.defaultKibanaIndex, + this.scopedClusterClient, + findResult.saved_objects.map(actionFromSavedObject) + ); + return { page: findResult.page, perPage: findResult.per_page, total: findResult.total, - data: findResult.saved_objects.map(actionFromSavedObject), + data, }; } @@ -155,9 +178,64 @@ export class ActionsClient { } } -function actionFromSavedObject(savedObject: SavedObject) { +function actionFromSavedObject(savedObject: SavedObject): ActionResult { return { id: savedObject.id, ...savedObject.attributes, }; } + +async function injectExtraFindData( + defaultKibanaIndex: string, + scopedClusterClient: IScopedClusterClient, + actionResults: ActionResult[] +): Promise { + const aggs: Record = {}; + for (const actionResult of actionResults) { + aggs[actionResult.id] = { + filter: { + bool: { + must: { + nested: { + path: 'references', + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { + 'references.id': actionResult.id, + }, + }, + { + term: { + 'references.type': 'action', + }, + }, + ], + }, + }, + }, + }, + }, + }, + }, + }, + }; + } + const aggregationResult = await scopedClusterClient.callAsInternalUser('search', { + index: defaultKibanaIndex, + body: { + aggs, + size: 0, + query: { + match_all: {}, + }, + }, + }); + return actionResults.map(actionResult => ({ + ...actionResult, + referencedByCount: aggregationResult.aggregations[actionResult.id].doc_count, + })); +} diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts index 4e2ef29dd740ff..8e6b1f19b172c1 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/send_email', () => ({ import { ActionType, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; import { sendEmail } from './lib/send_email'; import { ActionParamsType, ActionTypeConfigType, ActionTypeSecretsType } from './email'; @@ -23,7 +23,7 @@ const NO_OP_FN = () => {}; const services = { log: NO_OP_FN, callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionType: ActionType; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts index 57a107968ba70b..35d81ba74fa72d 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/send_email', () => ({ import { ActionType, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateParams } from '../lib'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; import { ActionParamsType, ActionTypeConfigType } from './es_index'; @@ -20,7 +20,7 @@ const NO_OP_FN = () => {}; const services = { log: NO_OP_FN, callCluster: jest.fn(), - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionType: ActionType; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/result_type.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/result_type.ts index c891f3bf218e7d..256463251315d8 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/result_type.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/result_type.ts @@ -8,16 +8,15 @@ // Which is basically the Haskel equivalent of Rust/ML/Scala's Result // I'll reach out to other's in Kibana to see if we can merge these into one type -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type Ok = { +export interface Ok { tag: 'ok'; value: T; -}; -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type Err = { +} + +export interface Err { tag: 'err'; error: E; -}; +} export type Result = Ok | Err; export function asOk(value: T): Ok { diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index a9f3ea757e33b5..1d453d2bd23408 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/post_pagerduty', () => ({ import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { postPagerduty } from './lib/post_pagerduty'; import { createActionTypeRegistry } from './index.test'; @@ -20,7 +20,7 @@ const ACTION_TYPE_ID = '.pagerduty'; const services: Services = { callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionType: ActionType; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts index e3feec6d1bc670..76e639c9948341 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -7,7 +7,7 @@ import { ActionType } from '../types'; import { validateParams } from '../lib'; import { Logger } from '../../../../../../src/core/server'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; const ACTION_TYPE_ID = '.server-log'; @@ -92,7 +92,7 @@ describe('execute()', () => { actionId, services: { callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }, params: { message: 'message text here', level: 'info' }, config: {}, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts index 681f508b1d2145..f6bd2d2b254df3 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts @@ -6,7 +6,7 @@ import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { ActionTypeRegistry } from '../action_type_registry'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { ActionExecutor, validateParams, validateSecrets, TaskRunnerFactory } from '../lib'; import { getActionType } from './slack'; import { taskManagerMock } from '../../../task_manager/task_manager.mock'; @@ -15,7 +15,7 @@ const ACTION_TYPE_ID = '.slack'; const services: Services = { callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionTypeRegistry: ActionTypeRegistry; diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts index e928eb491c1b5e..c6817b3bc12f3d 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts @@ -6,10 +6,10 @@ import { taskManagerMock } from '../../task_manager/task_manager.mock'; import { createExecuteFunction } from './create_execute_function'; -import { SavedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; const mockTaskManager = taskManagerMock.create(); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const getBasePath = jest.fn(); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts index 661a08df3dc30d..5ed67ae82b0ce5 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts @@ -8,14 +8,14 @@ import Hapi from 'hapi'; import { schema } from '@kbn/config-schema'; import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; -import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; import { - SavedObjectsClientMock, + savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; const actionExecutor = new ActionExecutor(); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); function getServices() { return { @@ -24,7 +24,7 @@ function getServices() { callCluster: jest.fn(), }; } -const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.create(); +const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); const actionTypeRegistry = actionTypeRegistryMock.create(); const executeParams = { diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts index aef389262f884d..b7d4ea96ea0f88 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts @@ -5,7 +5,7 @@ */ import Hapi from 'hapi'; -import { EncryptedSavedObjectsStartContract } from '../shim'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../../spaces'; import { Logger } from '../../../../../../src/core/server'; import { validateParams, validateConfig, validateSecrets } from './validate_with_schema'; diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index cc18c7b1694298..3e71725713070d 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -11,15 +11,15 @@ import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager'; import { TaskRunnerFactory } from './task_runner_factory'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { actionExecutorMock } from './action_executor.mock'; -import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; import { - SavedObjectsClientMock, + savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); -const mockedEncryptedSavedObjectsPlugin = encryptedSavedObjectsMock.create(); +const mockedEncryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); const mockedActionExecutor = actionExecutorMock.create(); let fakeTimer: sinon.SinonFakeTimers; @@ -54,7 +54,7 @@ afterAll(() => fakeTimer.restore()); const services = { log: jest.fn(), callCluster: jest.fn(), - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; const actionExecutorInitializerParams = { logger: loggingServiceMock.create().get(), diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts index efe9ce7fa8be5f..c0cca22b2c3eba 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts @@ -7,7 +7,7 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; import { RunContext } from '../../../task_manager'; -import { EncryptedSavedObjectsStartContract } from '../shim'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; import { ActionTaskParams, GetBasePathFunction, SpaceIdToNamespaceFunction } from '../types'; export interface TaskRunnerContext { diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index 618c1d120c37ae..26b65b1a1689a6 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -22,6 +22,7 @@ import { ActionsCoreStart, ActionsPluginsSetup, ActionsPluginsStart, + KibanaConfig, } from './shim'; import { createActionRoute, @@ -44,6 +45,7 @@ export interface PluginStartContract { } export class Plugin { + private readonly kibana$: Observable; private readonly config$: Observable; private readonly logger: Logger; private serverBasePath?: string; @@ -51,10 +53,12 @@ export class Plugin { private taskRunnerFactory?: TaskRunnerFactory; private actionTypeRegistry?: ActionTypeRegistry; private actionExecutor?: ActionExecutor; + private defaultKibanaIndex?: string; constructor(initializerContext: ActionsPluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); this.config$ = initializerContext.config.create(); + this.kibana$ = initializerContext.config.kibana$; } public async setup( @@ -63,6 +67,7 @@ export class Plugin { ): Promise { const config = await this.config$.pipe(first()).toPromise(); this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); + this.defaultKibanaIndex = (await this.kibana$.pipe(first()).toPromise()).index; plugins.xpack_main.registerFeature({ id: 'actions', @@ -92,12 +97,12 @@ export class Plugin { // - `secrets` properties will be encrypted // - `config` will be included in AAD // - everything else excluded from AAD - plugins.encrypted_saved_objects.registerType({ + plugins.encryptedSavedObjects.registerType({ type: 'action', attributesToEncrypt: new Set(['secrets']), attributesToExcludeFromAAD: new Set(['description']), }); - plugins.encrypted_saved_objects.registerType({ + plugins.encryptedSavedObjects.registerType({ type: 'action_task_params', attributesToEncrypt: new Set(['apiKey']), }); @@ -141,6 +146,7 @@ export class Plugin { adminClient, serverBasePath, taskRunnerFactory, + defaultKibanaIndex, } = this; function getServices(request: any): Services { @@ -163,11 +169,11 @@ export class Plugin { logger, spaces: plugins.spaces, getServices, - encryptedSavedObjectsPlugin: plugins.encrypted_saved_objects, + encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, actionTypeRegistry: actionTypeRegistry!, }); taskRunnerFactory!.initialize({ - encryptedSavedObjectsPlugin: plugins.encrypted_saved_objects, + encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, getBasePath, spaceIdToNamespace, }); @@ -186,6 +192,8 @@ export class Plugin { return new ActionsClient({ savedObjectsClient, actionTypeRegistry: actionTypeRegistry!, + defaultKibanaIndex: defaultKibanaIndex!, + scopedClusterClient: adminClient!.asScoped(request), }); }, }; diff --git a/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts b/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts index 340d341a5ef148..23356cedb3ab8c 100644 --- a/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts +++ b/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts @@ -5,10 +5,10 @@ */ import Hapi from 'hapi'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { actionsClientMock } from '../actions_client.mock'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; -import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; const defaultConfig = { 'kibana.index': '.kibana', @@ -21,8 +21,9 @@ export function createMockServer(config: Record = defaultConfig) { const actionsClient = actionsClientMock.create(); const actionTypeRegistry = actionTypeRegistryMock.create(); - const savedObjectsClient = SavedObjectsClientMock.create(); - const encryptedSavedObjects = encryptedSavedObjectsMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); + const encryptedSavedObjectsStart = encryptedSavedObjectsMock.createStart(); server.config = () => { return { @@ -49,21 +50,16 @@ export function createMockServer(config: Record = defaultConfig) { }, }); - server.register({ - name: 'encrypted_saved_objects', - register(pluginServer: Hapi.Server) { - pluginServer.expose('isEncryptionError', encryptedSavedObjects.isEncryptionError); - pluginServer.expose('registerType', encryptedSavedObjects.registerType); - pluginServer.expose( - 'getDecryptedAsInternalUser', - encryptedSavedObjects.getDecryptedAsInternalUser - ); - }, - }); - server.decorate('request', 'getSavedObjectsClient', () => savedObjectsClient); server.decorate('request', 'getActionsClient', () => actionsClient); server.decorate('request', 'getBasePath', () => '/s/my-space'); - return { server, savedObjectsClient, actionsClient, actionTypeRegistry, encryptedSavedObjects }; + return { + server, + savedObjectsClient, + actionsClient, + actionTypeRegistry, + encryptedSavedObjectsSetup, + encryptedSavedObjectsStart, + }; } diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts index c457a40a78b679..0da6b84f2cc691 100644 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ b/x-pack/legacy/plugins/actions/server/shim.ts @@ -12,7 +12,10 @@ import { TaskManager } from '../../task_manager'; import { XPackMainPlugin } from '../../xpack_main/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; -import { EncryptedSavedObjectsPlugin } from '../../encrypted_saved_objects'; +import { + PluginSetupContract as EncryptedSavedObjectsSetupContract, + PluginStartContract as EncryptedSavedObjectsStartContract, +} from '../../../../plugins/encrypted_saved_objects/server'; import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server'; import { CoreSetup, @@ -24,13 +27,16 @@ import { // due to being marked as dependencies interface Plugins extends Hapi.PluginProperties { task_manager: TaskManager; - encrypted_saved_objects: EncryptedSavedObjectsPlugin; } export interface Server extends Legacy.Server { plugins: Plugins; } +export interface KibanaConfig { + index: string; +} + /** * Shim what we're thinking setup and start contracts will look like */ @@ -38,15 +44,10 @@ export type TaskManagerStartContract = Pick; export type SecurityPluginSetupContract = Pick; export type SecurityPluginStartContract = Pick; -export type EncryptedSavedObjectsSetupContract = Pick; export type TaskManagerSetupContract = Pick< TaskManager, 'addMiddleware' | 'registerTaskDefinitions' >; -export type EncryptedSavedObjectsStartContract = Pick< - EncryptedSavedObjectsPlugin, - 'isEncryptionError' | 'getDecryptedAsInternalUser' ->; /** * New platform interfaces @@ -54,6 +55,7 @@ export type EncryptedSavedObjectsStartContract = Pick< export interface ActionsPluginInitializerContext { logger: LoggerFactory; config: { + kibana$: Rx.Observable; create(): Rx.Observable; }; } @@ -73,12 +75,12 @@ export interface ActionsPluginsSetup { security?: SecurityPluginSetupContract; task_manager: TaskManagerSetupContract; xpack_main: XPackMainPluginSetupContract; - encrypted_saved_objects: EncryptedSavedObjectsSetupContract; + encryptedSavedObjects: EncryptedSavedObjectsSetupContract; } export interface ActionsPluginsStart { security?: SecurityPluginStartContract; spaces: () => SpacesPluginStartContract | undefined; - encrypted_saved_objects: EncryptedSavedObjectsStartContract; + encryptedSavedObjects: EncryptedSavedObjectsStartContract; task_manager: TaskManagerStartContract; } @@ -101,6 +103,9 @@ export function shim( const initializerContext: ActionsPluginInitializerContext = { logger: newPlatform.coreContext.logger, config: { + kibana$: Rx.of({ + index: server.config().get('kibana.index'), + }), create() { return Rx.of({ enabled: server.config().get('xpack.actions.enabled') as boolean, @@ -126,7 +131,8 @@ export function shim( security: newPlatform.setup.plugins.security as SecurityPluginSetupContract | undefined, task_manager: server.plugins.task_manager, xpack_main: server.plugins.xpack_main, - encrypted_saved_objects: server.plugins.encrypted_saved_objects, + encryptedSavedObjects: newPlatform.setup.plugins + .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, }; const pluginsStart: ActionsPluginsStart = { @@ -134,7 +140,8 @@ export function shim( // TODO: Currently a function because it's an optional dependency that // initializes after this function is called spaces: () => server.plugins.spaces, - encrypted_saved_objects: server.plugins.encrypted_saved_objects, + encryptedSavedObjects: newPlatform.start.plugins + .encryptedSavedObjects as EncryptedSavedObjectsStartContract, task_manager: server.plugins.task_manager, }; diff --git a/x-pack/legacy/plugins/actions/server/types.ts b/x-pack/legacy/plugins/actions/server/types.ts index 9db89aea1b38d5..1ee5022338a465 100644 --- a/x-pack/legacy/plugins/actions/server/types.ts +++ b/x-pack/legacy/plugins/actions/server/types.ts @@ -45,6 +45,10 @@ export interface ActionResult { config: Record; } +export interface FindActionResult extends ActionResult { + referencedByCount: number; +} + // the result returned from an action type executor function export interface ActionTypeExecutorResult { status: 'ok' | 'error'; diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index ffe3185328abdb..1b90cb78d870c1 100644 --- a/x-pack/legacy/plugins/alerting/README.md +++ b/x-pack/legacy/plugins/alerting/README.md @@ -198,6 +198,7 @@ Payload: |Property|Description|Type| |---|---|---| |enabled|Indicate if you want the alert to start executing on an interval basis after it has been created.|boolean| +|name|A name to reference and search in the future.|string| |alertTypeId|The id value of the alert type you want to call when the alert is scheduled to execute.|string| |interval|The interval in seconds, minutes, hours or days the alert should execute. Example: `10s`, `5m`, `1h`, `1d`.|string| |alertTypeParams|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| @@ -242,6 +243,7 @@ Payload: |Property|Description|Type| |---|---|---| |interval|The interval in seconds, minutes, hours or days the alert should execute. Example: `10s`, `5m`, `1h`, `1d`.|string| +|name|A name to reference and search in the future.|string| |alertTypeParams|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): There map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| diff --git a/x-pack/legacy/plugins/alerting/index.ts b/x-pack/legacy/plugins/alerting/index.ts index 22db92baa8d28c..b3e33f782688c2 100644 --- a/x-pack/legacy/plugins/alerting/index.ts +++ b/x-pack/legacy/plugins/alerting/index.ts @@ -22,12 +22,12 @@ export function alerting(kibana: any) { return new kibana.Plugin({ id: 'alerting', configPrefix: 'xpack.alerting', - require: ['kibana', 'elasticsearch', 'actions', 'task_manager', 'encrypted_saved_objects'], + require: ['kibana', 'elasticsearch', 'actions', 'task_manager', 'encryptedSavedObjects'], isEnabled(config: Legacy.KibanaConfig) { return ( config.get('xpack.alerting.enabled') === true && config.get('xpack.actions.enabled') === true && - config.get('xpack.encrypted_saved_objects.enabled') === true && + config.get('xpack.encryptedSavedObjects.enabled') === true && config.get('xpack.task_manager.enabled') === true ); }, diff --git a/x-pack/legacy/plugins/alerting/mappings.json b/x-pack/legacy/plugins/alerting/mappings.json index bc648e874cfa4c..ff60ccc98cbaa6 100644 --- a/x-pack/legacy/plugins/alerting/mappings.json +++ b/x-pack/legacy/plugins/alerting/mappings.json @@ -4,6 +4,9 @@ "enabled": { "type": "boolean" }, + "name": { + "type": "text" + }, "alertTypeId": { "type": "keyword" }, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 574aed3fe93293..99af7db2ef8d75 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -6,13 +6,13 @@ import { schema } from '@kbn/config-schema'; import { AlertsClient } from './alerts_client'; -import { SavedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../task_manager/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; const taskManager = taskManagerMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const alertsClientParams = { taskManager, @@ -43,6 +43,7 @@ const mockedDate = new Date('2019-02-12T21:01:22.479Z'); function getMockData(overwrites: Record = {}) { return { enabled: true, + name: 'abc', alertTypeId: '123', interval: '10s', throttle: null, @@ -172,6 +173,7 @@ describe('create()', () => { "interval": "10s", "muteAll": false, "mutedInstanceIds": Array [], + "name": "abc", "throttle": null, "updatedBy": "elastic", } @@ -504,6 +506,7 @@ describe('create()', () => { }, ], alertTypeId: '123', + name: 'abc', alertTypeParams: { bar: true }, apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', @@ -1173,6 +1176,7 @@ describe('update()', () => { id: '1', data: { interval: '10s', + name: 'abc', alertTypeParams: { bar: true, }, @@ -1230,6 +1234,7 @@ describe('update()', () => { "apiKeyOwner": null, "enabled": true, "interval": "10s", + "name": "abc", "scheduledTaskId": "task-123", "updatedBy": "elastic", } @@ -1304,6 +1309,7 @@ describe('update()', () => { id: '1', data: { interval: '10s', + name: 'abc', alertTypeParams: { bar: true, }, @@ -1362,6 +1368,7 @@ describe('update()', () => { "apiKeyOwner": "elastic", "enabled": true, "interval": "10s", + "name": "abc", "scheduledTaskId": "task-123", "updatedBy": "elastic", } @@ -1406,6 +1413,7 @@ describe('update()', () => { id: '1', data: { interval: '10s', + name: 'abc', alertTypeParams: { bar: true, }, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index 39d0277ff53b3c..b92af43a1f1c63 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -72,6 +72,7 @@ interface CreateOptions { interface UpdateOptions { id: string; data: { + name: string; interval: string; actions: AlertAction[]; alertTypeParams: Record; diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts index ccc91ae2a90345..dcc74ed9488cea 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts @@ -9,9 +9,9 @@ import { schema } from '@kbn/config-schema'; import { AlertExecutorOptions } from '../types'; import { ConcreteTaskInstance } from '../../../task_manager'; import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; -import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; import { - SavedObjectsClientMock, + savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; @@ -51,8 +51,8 @@ beforeAll(() => { afterAll(() => fakeTimer.restore()); -const savedObjectsClient = SavedObjectsClientMock.create(); -const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); const services = { log: jest.fn(), callCluster: jest.fn(), diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts index ca11dc8533996d..0c6bd1b4a777a3 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts @@ -11,7 +11,7 @@ import { createAlertInstanceFactory } from './create_alert_instance_factory'; import { AlertInstance } from './alert_instance'; import { getNextRunAt } from './get_next_run_at'; import { validateAlertTypeParams } from './validate_alert_type_params'; -import { EncryptedSavedObjectsStartContract } from '../shim'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; import { AlertType, diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index d6c6d5907e7ac4..c50bc795757f3e 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -85,7 +85,7 @@ export class Plugin { }); // Encrypted attributes - plugins.encrypted_saved_objects.registerType({ + plugins.encryptedSavedObjects.registerType({ type: 'alert', attributesToEncrypt: new Set(['apiKey']), attributesToExcludeFromAAD: new Set([ @@ -147,7 +147,7 @@ export class Plugin { }; }, executeAction: plugins.actions.execute, - encryptedSavedObjectsPlugin: plugins.encrypted_saved_objects, + encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, spaceIdToNamespace(spaceId?: string): string | undefined { const spacesPlugin = plugins.spaces(); return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined; diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index 3751aa968b3de8..bd153150849c8a 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -12,6 +12,7 @@ server.route(createAlertRoute); const mockedAlert = { alertTypeId: '1', + name: 'abc', interval: '10s', alertTypeParams: { bar: true, @@ -44,24 +45,25 @@ test('creates an alert with proper parameters', async () => { expect(statusCode).toBe(200); const response = JSON.parse(payload); expect(response).toMatchInlineSnapshot(` + Object { + "actions": Array [ Object { - "actions": Array [ - Object { - "group": "default", - "id": "2", - "params": Object { - "foo": true, - }, - }, - ], - "alertTypeId": "1", - "alertTypeParams": Object { - "bar": true, + "group": "default", + "id": "2", + "params": Object { + "foo": true, }, - "id": "123", - "interval": "10s", - } - `); + }, + ], + "alertTypeId": "1", + "alertTypeParams": Object { + "bar": true, + }, + "id": "123", + "interval": "10s", + "name": "abc", + } + `); expect(alertsClient.create).toHaveBeenCalledTimes(1); expect(alertsClient.create.mock.calls[0]).toMatchInlineSnapshot(` Array [ @@ -82,6 +84,7 @@ test('creates an alert with proper parameters', async () => { }, "enabled": true, "interval": "10s", + "name": "abc", "throttle": null, }, }, @@ -107,6 +110,7 @@ test('creates an alert with proper parameters', async () => { }, "enabled": true, "interval": "10s", + "name": "abc", "throttle": null, }, }, diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.ts b/x-pack/legacy/plugins/alerting/server/routes/create.ts index 984153d81e0f86..14f72b0041e76f 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.ts @@ -12,6 +12,7 @@ import { getDurationSchema } from '../lib'; interface ScheduleRequest extends Hapi.Request { payload: { enabled: boolean; + name: string; alertTypeId: string; interval: string; actions: AlertAction[]; @@ -32,6 +33,7 @@ export const createAlertRoute = { payload: Joi.object() .keys({ enabled: Joi.boolean().default(true), + name: Joi.string().required(), alertTypeId: Joi.string().required(), throttle: getDurationSchema().default(null), interval: getDurationSchema().required(), diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts index 9e4f18fa1b40d2..2237d8245097c0 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts @@ -36,6 +36,7 @@ test('calls the update function with proper parameters', async () => { url: '/api/alert/1', payload: { throttle: null, + name: 'abc', interval: '12s', alertTypeParams: { otherField: false, @@ -75,6 +76,7 @@ test('calls the update function with proper parameters', async () => { "otherField": false, }, "interval": "12s", + "name": "abc", "throttle": null, }, "id": "1", diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.ts b/x-pack/legacy/plugins/alerting/server/routes/update.ts index 2b95b7bc340542..09362295ae73bd 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.ts @@ -15,6 +15,7 @@ interface UpdateRequest extends Hapi.Request { }; payload: { alertTypeId: string; + name: string; interval: string; actions: AlertAction[]; alertTypeParams: Record; @@ -36,6 +37,7 @@ export const updateAlertRoute = { throttle: getDurationSchema() .required() .allow(null), + name: Joi.string().required(), interval: getDurationSchema().required(), alertTypeParams: Joi.object().required(), actions: Joi.array() diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts index c977fda451df18..d86eab2038095d 100644 --- a/x-pack/legacy/plugins/alerting/server/shim.ts +++ b/x-pack/legacy/plugins/alerting/server/shim.ts @@ -10,7 +10,10 @@ import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; import { TaskManager } from '../../task_manager'; import { XPackMainPlugin } from '../../xpack_main/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; -import { EncryptedSavedObjectsPlugin } from '../../encrypted_saved_objects'; +import { + PluginSetupContract as EncryptedSavedObjectsSetupContract, + PluginStartContract as EncryptedSavedObjectsStartContract, +} from '../../../../plugins/encrypted_saved_objects/server'; import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server'; import { CoreSetup, @@ -28,7 +31,6 @@ import { interface Plugins extends Hapi.PluginProperties { actions: ActionsPlugin; task_manager: TaskManager; - encrypted_saved_objects: EncryptedSavedObjectsPlugin; } export interface Server extends Legacy.Server { @@ -41,16 +43,11 @@ export interface Server extends Legacy.Server { export type TaskManagerStartContract = Pick; export type SecurityPluginSetupContract = Pick; export type SecurityPluginStartContract = Pick; -export type EncryptedSavedObjectsSetupContract = Pick; export type XPackMainPluginSetupContract = Pick; export type TaskManagerSetupContract = Pick< TaskManager, 'addMiddleware' | 'registerTaskDefinitions' >; -export type EncryptedSavedObjectsStartContract = Pick< - EncryptedSavedObjectsPlugin, - 'isEncryptionError' | 'getDecryptedAsInternalUser' ->; /** * New platform interfaces @@ -75,13 +72,13 @@ export interface AlertingPluginsSetup { task_manager: TaskManagerSetupContract; actions: ActionsPluginSetupContract; xpack_main: XPackMainPluginSetupContract; - encrypted_saved_objects: EncryptedSavedObjectsSetupContract; + encryptedSavedObjects: EncryptedSavedObjectsSetupContract; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; security?: SecurityPluginStartContract; spaces: () => SpacesPluginStartContract | undefined; - encrypted_saved_objects: EncryptedSavedObjectsStartContract; + encryptedSavedObjects: EncryptedSavedObjectsStartContract; task_manager: TaskManagerStartContract; } @@ -122,7 +119,8 @@ export function shim( task_manager: server.plugins.task_manager, actions: server.plugins.actions.setup, xpack_main: server.plugins.xpack_main, - encrypted_saved_objects: server.plugins.encrypted_saved_objects, + encryptedSavedObjects: newPlatform.setup.plugins + .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, }; const pluginsStart: AlertingPluginsStart = { @@ -131,7 +129,8 @@ export function shim( // TODO: Currently a function because it's an optional dependency that // initializes after this function is called spaces: () => server.plugins.spaces, - encrypted_saved_objects: server.plugins.encrypted_saved_objects, + encryptedSavedObjects: newPlatform.start.plugins + .encryptedSavedObjects as EncryptedSavedObjectsStartContract, task_manager: server.plugins.task_manager, }; diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index 3c71412da2c899..94b81c9e1b576d 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -60,6 +60,7 @@ export interface RawAlertAction extends SavedObjectAttributes { export interface Alert { enabled: boolean; + name: string; alertTypeId: string; interval: string; actions: AlertAction[]; @@ -76,6 +77,7 @@ export interface Alert { export interface RawAlert extends SavedObjectAttributes { enabled: boolean; + name: string; alertTypeId: string; interval: string; actions: RawAlertAction[]; diff --git a/x-pack/legacy/plugins/apm/common/projections/errors.ts b/x-pack/legacy/plugins/apm/common/projections/errors.ts index c3094f5cbb0b66..adbd2eb1d6d27d 100644 --- a/x-pack/legacy/plugins/apm/common/projections/errors.ts +++ b/x-pack/legacy/plugins/apm/common/projections/errors.ts @@ -19,10 +19,10 @@ export function getErrorGroupsProjection({ setup: Setup; serviceName: string; }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; return { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { query: { bool: { diff --git a/x-pack/legacy/plugins/apm/common/projections/metrics.ts b/x-pack/legacy/plugins/apm/common/projections/metrics.ts index 5c9eeb54744d7f..25d1484624e152 100644 --- a/x-pack/legacy/plugins/apm/common/projections/metrics.ts +++ b/x-pack/legacy/plugins/apm/common/projections/metrics.ts @@ -34,7 +34,7 @@ export function getMetricsProjection({ serviceName: string; serviceNodeName?: string; }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; const filter = [ { term: { [SERVICE_NAME]: serviceName } }, @@ -45,7 +45,7 @@ export function getMetricsProjection({ ]; return { - index: config.get('apm_oss.metricsIndices'), + index: indices['apm_oss.metricsIndices'], body: { query: { bool: { diff --git a/x-pack/legacy/plugins/apm/common/projections/services.ts b/x-pack/legacy/plugins/apm/common/projections/services.ts index ab72211f92aa71..e889899e116347 100644 --- a/x-pack/legacy/plugins/apm/common/projections/services.ts +++ b/x-pack/legacy/plugins/apm/common/projections/services.ts @@ -9,13 +9,13 @@ import { SERVICE_NAME, PROCESSOR_EVENT } from '../elasticsearch_fieldnames'; import { rangeFilter } from '../../server/lib/helpers/range_filter'; export function getServicesProjection({ setup }: { setup: Setup }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; return { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/common/projections/transactions.ts b/x-pack/legacy/plugins/apm/common/projections/transactions.ts index 63abb0572df878..fb249340c867c9 100644 --- a/x-pack/legacy/plugins/apm/common/projections/transactions.ts +++ b/x-pack/legacy/plugins/apm/common/projections/transactions.ts @@ -24,7 +24,7 @@ export function getTransactionsProjection({ transactionName?: string; transactionType?: string; }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; const transactionNameFilter = transactionName ? [{ term: { [TRANSACTION_NAME]: transactionName } }] @@ -48,7 +48,7 @@ export function getTransactionsProjection({ }; return { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { query: { bool diff --git a/x-pack/legacy/plugins/apm/common/projections/typings.ts b/x-pack/legacy/plugins/apm/common/projections/typings.ts index 0c56fd2f75bdef..2b55395b70c6ba 100644 --- a/x-pack/legacy/plugins/apm/common/projections/typings.ts +++ b/x-pack/legacy/plugins/apm/common/projections/typings.ts @@ -4,15 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; +import { ESSearchRequest, ESSearchBody } from '../../typings/elasticsearch'; +import { + AggregationOptionsByType, + AggregationInputMap +} from '../../typings/elasticsearch/aggregations'; -export type Projection = Omit & { - body: { - query: any; - } & { +export type Projection = Omit & { + body: Omit & { aggs?: { [key: string]: { - terms: any; + terms: AggregationOptionsByType['terms']; + aggs?: AggregationInputMap; }; }; }; diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts index ae1b7c552ab4e4..aa72b5fd71365a 100644 --- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts +++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts @@ -29,12 +29,15 @@ describe('mergeProjection', () => { }); it('merges plain objects', () => { + const termsAgg = { terms: { field: 'bar' } }; expect( mergeProjection( - { body: { query: {}, aggs: { foo: { terms: { field: 'bar' } } } } }, + { body: { query: {}, aggs: { foo: termsAgg } } }, { body: { - aggs: { foo: { aggs: { bar: { terms: { field: 'baz' } } } } } + aggs: { + foo: { ...termsAgg, aggs: { bar: { terms: { field: 'baz' } } } } + } } } ) diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts index 5b6b5b0b7f058d..9a8f11c6493c50 100644 --- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts +++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts @@ -4,10 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ import { merge, isPlainObject } from 'lodash'; +import { DeepPartial } from 'utility-types'; +import { AggregationInputMap } from '../../../../typings/elasticsearch/aggregations'; +import { + ESSearchRequest, + ESSearchBody +} from '../../../../typings/elasticsearch'; import { Projection } from '../../typings'; type PlainObject = Record; +type SourceProjection = Omit, 'body'> & { + body: Omit, 'aggs'> & { + aggs?: AggregationInputMap; + }; +}; + type DeepMerge = U extends PlainObject ? (T extends PlainObject ? (Omit & @@ -19,10 +31,10 @@ type DeepMerge = U extends PlainObject : U) : U; -export function mergeProjection( - target: T, - source: U -): DeepMerge { +export function mergeProjection< + T extends Projection, + U extends SourceProjection +>(target: T, source: U): DeepMerge { return merge({}, target, source, (a, b) => { if (isPlainObject(a) && isPlainObject(b)) { return undefined; diff --git a/x-pack/legacy/plugins/apm/dev_docs/github_commands.md b/x-pack/legacy/plugins/apm/dev_docs/github_commands.md new file mode 100644 index 00000000000000..f2c32bafa7539d --- /dev/null +++ b/x-pack/legacy/plugins/apm/dev_docs/github_commands.md @@ -0,0 +1,6 @@ +### Useful Github Pull Request commands + +The following commands can be executed by writing them as comments on a pull request: + +- `@elasticmachine merge upstream`: Will merge the upstream (eg. master or 7.x) into the branch. This is useful if a bug has been fixed upstream and the fix is necessary to pass CI checks +- `retest` Re-run the tests. This is useful if a flaky test caused the build to fail diff --git a/x-pack/legacy/plugins/apm/dev_docs/typescript.md b/x-pack/legacy/plugins/apm/dev_docs/typescript.md new file mode 100644 index 00000000000000..105c6edabf48ff --- /dev/null +++ b/x-pack/legacy/plugins/apm/dev_docs/typescript.md @@ -0,0 +1,11 @@ +#### Optimizing TypeScript + +Kibana and X-Pack are very large TypeScript projects, and it comes at a cost. Editor responsiveness is not great, and the CLI type check for X-Pack takes about a minute. To get faster feedback, we create a smaller APM TypeScript project that only type checks the APM project and the files it uses. This optimization consists of creating a `tsconfig.json` in APM that includes the Kibana/X-Pack typings, and editing the Kibana/X-Pack configurations to not include any files, or removing the configurations altogether. The script configures git to ignore any changes in these files, and has an undo script as well. + +To run the optimization: + +`$ node x-pack/legacy/plugins/apm/scripts/optimize-tsconfig` + +To undo the optimization: + +`$ node x-pack/legacy/plugins/apm/scripts/unoptimize-tsconfig` diff --git a/x-pack/legacy/plugins/apm/dev_docs/vscode_setup.md b/x-pack/legacy/plugins/apm/dev_docs/vscode_setup.md new file mode 100644 index 00000000000000..e1901b3855f735 --- /dev/null +++ b/x-pack/legacy/plugins/apm/dev_docs/vscode_setup.md @@ -0,0 +1,53 @@ +### Visual Studio Code + +When using [Visual Studio Code](https://code.visualstudio.com/) with APM it's best to set up a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) and add the `x-pack/legacy/plugins/apm` directory, the `x-pack` directory, and the root of the Kibana repository to the workspace. This makes it so you can navigate and search within APM and use the wider workspace roots when you need to widen your search. + +#### Using the Jest extension + +The [vscode-jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) is a good way to run your Jest tests inside the editor. + +Some of the benefits of using the extension over just running it in a terminal are: + +• It shows the pass/fail of a test inline in the test file +• It shows the error message in the test file if it fails +• You don’t have to have the terminal process running +• It can automatically update your snapshots when they change +• Coverage mapping + +The extension doesn't really work well if you're trying to use it on all of Kibana or all of X-Pack, but it works well if you configure it to run only on the files in APM. + +If you have a workspace configured as described above you should have: + +```json +"jest.disabledWorkspaceFolders": ["kibana", "x-pack"] +``` + +in your Workspace settings, and: + +```json +"jest.pathToJest": "node scripts/jest.js --testPathPattern=legacy/plugins/apm", +"jest.rootPath": "../../.." +``` + +in the settings for the APM folder. + +#### Jest debugging + +To make the [VSCode debugger](https://vscode.readthedocs.io/en/latest/editor/debugging/) work with Jest (you can set breakpoints in the code and tests and use the VSCode debugger) you'll need the [Node Debug extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.node-debug2) installed and can set up a launch configuration like: + +```json +{ + "type": "node", + "name": "APM Jest", + "request": "launch", + "args": ["--runInBand", "--testPathPattern=legacy/plugins/apm"], + "cwd": "${workspaceFolder}/../../..", + "console": "internalConsole", + "internalConsoleOptions": "openOnSessionStart", + "disableOptimisticBPs": true, + "program": "${workspaceFolder}/../../../scripts/jest.js", + "runtimeVersion": "10.15.2" +} +``` + +(you'll want `runtimeVersion` to match what's in the Kibana root .nvmrc. Depending on your setup, you might be able to remove this line.) diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 4d3d2b210bbae6..556bce9d37bb54 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -43,8 +43,7 @@ export const apm: LegacyPluginInitializer = kibana => { apmUiEnabled: config.get('xpack.apm.ui.enabled'), // TODO: rename to apm_oss.indexPatternTitle in 7.0 (breaking change) apmIndexPatternTitle: config.get('apm_oss.indexPattern'), - apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled'), - apmTransactionIndices: config.get('apm_oss.transactionIndices') + apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled') }; }, hacks: ['plugins/apm/hacks/toggle_app_link_in_nav'], diff --git a/x-pack/legacy/plugins/apm/mappings.json b/x-pack/legacy/plugins/apm/mappings.json index 26075d406c993a..0b31798242fadc 100644 --- a/x-pack/legacy/plugins/apm/mappings.json +++ b/x-pack/legacy/plugins/apm/mappings.json @@ -41,5 +41,30 @@ } } } + }, + "apm-indices": { + "properties": { + "apm_oss.sourcemapIndices": { + "type": "keyword" + }, + "apm_oss.errorIndices": { + "type": "keyword" + }, + "apm_oss.onboardingIndices": { + "type": "keyword" + }, + "apm_oss.spanIndices": { + "type": "keyword" + }, + "apm_oss.transactionIndices": { + "type": "keyword" + }, + "apm_oss.metricsIndices": { + "type": "keyword" + }, + "apm_oss.apmAgentConfigurationIndex": { + "type": "keyword" + } + } } } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx index 253580ce5cecdf..33b20b0f0f2265 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx @@ -17,7 +17,7 @@ export interface ErrorTab { export const logStacktraceTab: ErrorTab = { key: 'log_stacktrace', label: i18n.translate('xpack.apm.propertiesTable.tabs.logStacktraceLabel', { - defaultMessage: 'Log stacktrace' + defaultMessage: 'Log stack trace' }) }; @@ -26,7 +26,7 @@ export const exceptionStacktraceTab: ErrorTab = { label: i18n.translate( 'xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel', { - defaultMessage: 'Exception stacktrace' + defaultMessage: 'Exception stack trace' } ) }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx new file mode 100644 index 00000000000000..8ec9c604e7038f --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { ExceptionStacktrace } from './ExceptionStacktrace'; + +describe('ExceptionStacktrace', () => { + describe('render', () => { + it('renders', () => { + const props = { exceptions: [] }; + + expect(() => + shallow() + ).not.toThrowError(); + }); + + describe('with a stack trace', () => { + it('renders the stack trace', () => { + const props = { exceptions: [{}] }; + + expect( + shallow().find('Stacktrace') + ).toHaveLength(1); + }); + }); + + describe('with more than one stack trace', () => { + it('renders a cause stack trace', () => { + const props = { exceptions: [{}, {}] }; + + expect( + shallow().find('CauseStacktrace') + ).toHaveLength(1); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx new file mode 100644 index 00000000000000..13c904a1194499 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiTitle } from '@elastic/eui'; +import { idx } from '@kbn/elastic-idx/target'; +import { Exception } from '../../../../../typings/es_schemas/raw/ErrorRaw'; +import { Stacktrace } from '../../../shared/Stacktrace'; +import { CauseStacktrace } from '../../../shared/Stacktrace/CauseStacktrace'; + +interface ExceptionStacktraceProps { + codeLanguage?: string; + exceptions: Exception[]; +} + +export function ExceptionStacktrace({ + codeLanguage, + exceptions +}: ExceptionStacktraceProps) { + const title = idx(exceptions, _ => _[0].message); + + return ( + <> + +

{title}

+
+ {exceptions.map((ex, index) => { + return index === 0 ? ( + + ) : ( + + ); + })} + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap index be9d67f31cb113..39874c11b09bf6 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap @@ -46,7 +46,7 @@ exports[`DetailView should render TabContent 1`] = ` currentTab={ Object { "key": "exception_stacktrace", - "label": "Exception stacktrace", + "label": "Exception stack trace", } } error={ @@ -71,7 +71,7 @@ exports[`DetailView should render tabs 1`] = ` key="exception_stacktrace" onClick={[Function]} > - Exception stacktrace + Exception stack trace _.service.language.name); - const excStackframes = idx(error, _ => _.error.exception[0].stacktrace); + const exceptions = idx(error, _ => _.error.exception) || []; const logStackframes = idx(error, _ => _.error.log.stacktrace); switch (currentTab.key) { @@ -198,7 +199,10 @@ export function TabContent({ ); case exceptionStacktraceTab.key: return ( - + ); default: return ; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index 2689ce8bd9424d..c663c52d7d639e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -132,7 +132,11 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` data-test-subj="tableHeaderCell_groupId_0" role="columnheader" scope="col" - width="96px" + style={ + Object { + "width": "96px", + } + } >
List should render empty state 1`] = ` data-test-subj="tableHeaderCell_message_1" role="columnheader" scope="col" - width="50%" + style={ + Object { + "width": "50%", + } + } >
List should render empty state 1`] = ` data-test-subj="tableHeaderCell_handled_2" role="columnheader" scope="col" + style={ + Object { + "width": undefined, + } + } >
List should render empty state 1`] = ` data-test-subj="tableHeaderCell_occurrenceCount_3" role="columnheader" scope="col" + style={ + Object { + "width": undefined, + } + } > diff --git a/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx index aef8e5747d992c..412b92d525aa2e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx @@ -168,7 +168,7 @@ const Filter = ({ }} value={value} /> - + ) : null} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx index 0f5fcceea3d200..29a8528295dd78 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -8,7 +8,6 @@ import { EuiBasicTable } from '@elastic/eui'; import { sortByOrder } from 'lodash'; import React, { useMemo, useCallback, ReactNode } from 'react'; import { idx } from '@kbn/elastic-idx'; -import { StringMap } from '../../../../typings/common'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; import { fromQuery, toQuery } from '../Links/url_helpers'; @@ -16,7 +15,7 @@ import { fromQuery, toQuery } from '../Links/url_helpers'; // TODO: this should really be imported from EUI export interface ITableColumn { name: ReactNode; - actions?: StringMap[]; + actions?: Array>; field?: string; dataType?: string; align?: string; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx index 652bb25afed8ea..04a5b604677304 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { ERROR_METADATA_SECTIONS } from './sections'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; +import { getSectionsWithRows } from '../helper'; import { MetadataTable } from '..'; interface Props { @@ -14,5 +15,9 @@ interface Props { } export function ErrorMetadata({ error }: Props) { - return ; + const sectionsWithRows = useMemo( + () => getSectionsWithRows(ERROR_METADATA_SECTIONS, error), + [error] + ); + return ; } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts index 526bb4e5bcf71c..1eeebc8543d72f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/sections.ts @@ -20,8 +20,8 @@ import { } from '../sections'; export const ERROR_METADATA_SECTIONS: Section[] = [ - ERROR, { ...LABELS, required: true }, + ERROR, HTTP, HOST, CONTAINER, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx new file mode 100644 index 00000000000000..6f67b2458ea102 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import { KeyValueTable } from '../KeyValueTable'; +import { KeyValuePair } from '../../../utils/flattenObject'; + +interface Props { + keyValuePairs?: KeyValuePair[]; +} + +export function Section({ keyValuePairs }: Props) { + if (keyValuePairs) { + return ; + } + return ( + + {i18n.translate( + 'xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel', + { defaultMessage: 'No data available' } + )} + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx index 713863639d1b7d..03182062d324ac 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { SPAN_METADATA_SECTIONS } from './sections'; import { Span } from '../../../../../typings/es_schemas/ui/Span'; +import { getSectionsWithRows } from '../helper'; import { MetadataTable } from '..'; interface Props { @@ -14,5 +15,9 @@ interface Props { } export function SpanMetadata({ span }: Props) { - return ; + const sectionsWithRows = useMemo( + () => getSectionsWithRows(SPAN_METADATA_SECTIONS, span), + [span] + ); + return ; } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts index 01e56bdb09f19a..7012bbcc8fceaf 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts @@ -15,10 +15,10 @@ import { } from '../sections'; export const SPAN_METADATA_SECTIONS: Section[] = [ + LABELS, SPAN, - AGENT, - SERVICE, TRANSACTION, - LABELS, - TRACE + TRACE, + SERVICE, + AGENT ]; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx index fa2b32b403ee79..4216e37e0cb27e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { TRANSACTION_METADATA_SECTIONS } from './sections'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; +import { getSectionsWithRows } from '../helper'; import { MetadataTable } from '..'; interface Props { @@ -14,10 +15,9 @@ interface Props { } export function TransactionMetadata({ transaction }: Props) { - return ( - + const sectionsWithRows = useMemo( + () => getSectionsWithRows(TRANSACTION_METADATA_SECTIONS, transaction), + [transaction] ); + return ; } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts index 04a0d64077c156..6b30c82bc35a06 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts @@ -22,8 +22,8 @@ import { } from '../sections'; export const TRANSACTION_METADATA_SECTIONS: Section[] = [ - TRANSACTION, { ...LABELS, required: true }, + TRANSACTION, HTTP, HOST, CONTAINER, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index 331a8bf41642ec..bdf895f423913e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -8,77 +8,44 @@ import React from 'react'; import 'jest-dom/extend-expect'; import { render, cleanup } from 'react-testing-library'; import { MetadataTable } from '..'; -import { - expectTextsInDocument, - expectTextsNotInDocument -} from '../../../../utils/testHelpers'; -import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; +import { expectTextsInDocument } from '../../../../utils/testHelpers'; +import { SectionsWithRows } from '../helper'; describe('MetadataTable', () => { afterEach(cleanup); + it('shows sections', () => { + const sectionsWithRows = ([ + { key: 'foo', label: 'Foo', required: true }, + { + key: 'bar', + label: 'Bar', + required: false, + properties: ['props.A', 'props.B'], + rows: [{ key: 'props.A', value: 'A' }, { key: 'props.B', value: 'B' }] + } + ] as unknown) as SectionsWithRows; + const output = render(); + expectTextsInDocument(output, [ + 'Foo', + 'No data available', + 'Bar', + 'props.A', + 'A', + 'props.B', + 'B' + ]); + }); describe('required sections', () => { it('shows "empty state message" if no data is available', () => { - const sections = [ + const sectionsWithRows = ([ { key: 'foo', label: 'Foo', required: true } - ]; - const output = render( - - ); + ] as unknown) as SectionsWithRows; + const output = render(); expectTextsInDocument(output, ['Foo', 'No data available']); }); - it('shows "empty state message" if property is not available', () => { - const sections = [ - { - key: 'foo', - label: 'Foo', - required: true, - properties: ['bar'] - } - ]; - const item = ({ - foo: { - foobar: 'bar' - } - } as unknown) as Transaction; - - const output = render(); - expectTextsInDocument(output, ['Foo', 'No data available']); - }); - }); - describe('not required sections', () => { - it('does not show section when no items are provided', () => { - const sections = [ - { - key: 'foo', - label: 'Foo', - required: false - } - ]; - const output = render( - - ); - expectTextsNotInDocument(output, ['Foo']); - }); - it('does not show section if property is not available', () => { - const sections = [ - { - key: 'foo', - label: 'Foo', - required: false, - properties: ['bar'] - } - ]; - const item = ({ - foo: { - foobar: 'bar' - } - } as unknown) as Transaction; - const output = render(); - expectTextsNotInDocument(output, ['Foo']); - }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx new file mode 100644 index 00000000000000..7e68b2f84eeadf --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import 'jest-dom/extend-expect'; +import { render } from 'react-testing-library'; +import { Section } from '../Section'; +import { expectTextsInDocument } from '../../../../utils/testHelpers'; + +describe('Section', () => { + it('shows "empty state message" if no data is available', () => { + const output = render(
); + expectTextsInDocument(output, ['No data available']); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts new file mode 100644 index 00000000000000..aaf73619e481a4 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getSectionsWithRows, filterSectionsByTerm } from '../helper'; +import { LABELS, HTTP, SERVICE } from '../sections'; +import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; + +describe('MetadataTable Helper', () => { + const sections = [ + { ...LABELS, required: true }, + HTTP, + { ...SERVICE, properties: ['environment'] } + ]; + const apmDoc = ({ + http: { + headers: { + Connection: 'close', + Host: 'opbeans:3000', + request: { method: 'get' } + } + }, + service: { + framework: { name: 'express' }, + environment: 'production' + } + } as unknown) as Transaction; + const metadataItems = getSectionsWithRows(sections, apmDoc); + + it('returns flattened data and required section', () => { + expect(metadataItems).toEqual([ + { key: 'labels', label: 'Labels', required: true, rows: [] }, + { + key: 'http', + label: 'HTTP', + rows: [ + { key: 'http.headers.Connection', value: 'close' }, + { key: 'http.headers.Host', value: 'opbeans:3000' }, + { key: 'http.headers.request.method', value: 'get' } + ] + }, + { + key: 'service', + label: 'Service', + properties: ['environment'], + rows: [{ key: 'service.environment', value: 'production' }] + } + ]); + }); + describe('filter', () => { + it('items by key', () => { + const filteredItems = filterSectionsByTerm(metadataItems, 'http'); + expect(filteredItems).toEqual([ + { + key: 'http', + label: 'HTTP', + rows: [ + { key: 'http.headers.Connection', value: 'close' }, + { key: 'http.headers.Host', value: 'opbeans:3000' }, + { key: 'http.headers.request.method', value: 'get' } + ] + } + ]); + }); + + it('items by value', () => { + const filteredItems = filterSectionsByTerm(metadataItems, 'product'); + expect(filteredItems).toEqual([ + { + key: 'service', + label: 'Service', + properties: ['environment'], + rows: [{ key: 'service.environment', value: 'production' }] + } + ]); + }); + + it('returns empty when no item matches', () => { + const filteredItems = filterSectionsByTerm(metadataItems, 'post'); + expect(filteredItems).toEqual([]); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/helper.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/helper.ts new file mode 100644 index 00000000000000..c272790826d8dd --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/helper.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get, pick, isEmpty } from 'lodash'; +import { Section } from './sections'; +import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; +import { APMError } from '../../../../typings/es_schemas/ui/APMError'; +import { Span } from '../../../../typings/es_schemas/ui/Span'; +import { flattenObject, KeyValuePair } from '../../../utils/flattenObject'; + +export type SectionsWithRows = ReturnType; + +export const getSectionsWithRows = ( + sections: Section[], + apmDoc: Transaction | APMError | Span +) => { + return sections + .map(section => { + const sectionData: Record = get(apmDoc, section.key); + const filteredData: + | Record + | undefined = section.properties + ? pick(sectionData, section.properties) + : sectionData; + + const rows: KeyValuePair[] = flattenObject(filteredData, section.key); + return { ...section, rows }; + }) + .filter(({ required, rows }) => required || !isEmpty(rows)); +}; + +export const filterSectionsByTerm = ( + sections: SectionsWithRows, + searchTerm: string +) => { + if (!searchTerm) { + return sections; + } + return sections + .map(section => { + const { rows = [] } = section; + const filteredRows = rows.filter(({ key, value }) => { + const valueAsString = String(value).toLowerCase(); + return ( + key.toLowerCase().includes(searchTerm) || + valueAsString.includes(searchTerm) + ); + }); + return { ...section, rows: filteredRows }; + }) + .filter(({ rows }) => !isEmpty(rows)); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx index d7f3bf5504d405..53d54ae5de7ad1 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx @@ -9,42 +9,50 @@ import { EuiFlexItem, EuiIcon, EuiSpacer, - EuiTitle + EuiTitle, + EuiFieldSearch } from '@elastic/eui'; -import React from 'react'; -import { get, pick, isEmpty } from 'lodash'; -import { EuiText } from '@elastic/eui'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { DottedKeyValueTable } from '../DottedKeyValueTable'; +import { isEmpty } from 'lodash'; +import { EuiText } from '@elastic/eui'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; -import { Section as SectionType } from './sections'; -import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; -import { APMError } from '../../../../typings/es_schemas/ui/APMError'; -import { Span } from '../../../../typings/es_schemas/ui/Span'; - -type Item = Transaction | APMError | Span; +import { HeightRetainer } from '../HeightRetainer'; +import { Section } from './Section'; +import { history } from '../../../utils/history'; +import { fromQuery, toQuery } from '../Links/url_helpers'; +import { useLocation } from '../../../hooks/useLocation'; +import { useUrlParams } from '../../../hooks/useUrlParams'; +import { SectionsWithRows, filterSectionsByTerm } from './helper'; interface Props { - item: Item; - sections: SectionType[]; + sections: SectionsWithRows; } -const filterSections = (sections: SectionType[], item: Item) => - sections - .map(section => { - const data: Record = get(item, section.key); - return { - ...section, - data: section.properties ? pick(data, section.properties) : data - }; - }) - .filter(({ required, data }) => required || !isEmpty(data)); +export function MetadataTable({ sections }: Props) { + const location = useLocation(); + const { urlParams } = useUrlParams(); + const { searchTerm = '' } = urlParams; -export function MetadataTable({ item, sections }: Props) { - const filteredSections = filterSections(sections, item); + const filteredSections = filterSectionsByTerm(sections, searchTerm); + + const onSearchChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value.trim().toLowerCase(); + history.replace({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + searchTerm: value + }) + }); + }, + [location] + ); + const noResultFound = Boolean(searchTerm) && isEmpty(filteredSections); return ( - + @@ -52,40 +60,49 @@ export function MetadataTable({ item, sections }: Props) { + + + - {filteredSections.map(section => ( -
- -
{section.label}
-
- -
- -
- ))} + + {filteredSections.map(section => ( +
+ +
{section.label}
+
+ +
+ +
+ ))} + {noResultFound && } +
); } -function Section({ - propData = {}, - propKey -}: { - propData?: Record; - propKey?: string; -}) { - if (isEmpty(propData)) { - return ( +const NoResultFound = ({ value }: { value: string }) => ( + + {i18n.translate( - 'xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel', - { defaultMessage: 'No data available' } + 'xpack.apm.propertiesTable.agentFeature.noResultFound', + { + defaultMessage: `No results for "{value}".`, + values: { value } + } )} - ); - } - - return ( - - ); -} + + +); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx new file mode 100644 index 00000000000000..f36b91a522e928 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import { CauseStacktrace } from './CauseStacktrace'; + +describe('CauseStacktrace', () => { + describe('render', () => { + describe('with no stack trace', () => { + it('renders without the accordion', () => { + const props = { id: 'testId', message: 'testMessage' }; + + expect( + mount().find('CausedBy') + ).toHaveLength(1); + }); + }); + + describe('with no message and a stack trace', () => { + it('says "Caused by …', () => { + const props = { + id: 'testId', + stackframes: [{ filename: 'testFilename', line: { number: 1 } }] + }; + + expect( + mount() + .find('EuiTitle span') + .text() + ).toEqual('…'); + }); + }); + + describe('with a message and a stack trace', () => { + it('renders with the accordion', () => { + const props = { + id: 'testId', + message: 'testMessage', + stackframes: [{ filename: 'testFilename', line: { number: 1 } }] + }; + + expect( + shallow().find('Styled(EuiAccordion)') + ).toHaveLength(1); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx new file mode 100644 index 00000000000000..52f2ba45867181 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiTitle } from '@elastic/eui'; +import { px, unit } from '../../../style/variables'; +import { Stacktrace } from '.'; +import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; + +// @ts-ignore Styled Components has trouble inferring the types of the default props here. +const Accordion = styled(EuiAccordion)` + border-top: ${theme.euiBorderThin}; +`; + +const CausedByContainer = styled('h5')` + padding: ${theme.spacerSizes.s} 0; +`; + +const CausedByHeading = styled('span')` + color: ${theme.textColors.subdued}; + display: block; + font-size: ${theme.euiFontSizeXS}; + font-weight: ${theme.euiFontWeightBold}; + text-transform: uppercase; +`; + +const FramesContainer = styled('div')` + padding-left: ${px(unit)}; +`; + +function CausedBy({ message }: { message: string }) { + return ( + + + {i18n.translate( + 'xpack.apm.stacktraceTab.causedByFramesToogleButtonLabel', + { + defaultMessage: 'Caused By' + } + )} + + + {message} + + + ); +} + +interface CauseStacktraceProps { + codeLanguage?: string; + id: string; + message?: string; + stackframes?: IStackframe[]; +} + +export function CauseStacktrace({ + codeLanguage, + id, + message = '…', + stackframes = [] +}: CauseStacktraceProps) { + if (stackframes.length === 0) { + return ; + } + + return ( + } id={id}> + + + + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx index c5d610d0b8e1b4..9685ba920a73ce 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx @@ -32,7 +32,7 @@ registerLanguage('ruby', ruby); const ContextContainer = styled.div` position: relative; - border-radius: 0 0 ${borderRadius} ${borderRadius}; + border-radius: ${borderRadius}; `; const LINE_HEIGHT = units.eighth * 9; @@ -49,7 +49,7 @@ const LineNumberContainer = styled.div<{ isLibraryFrame: boolean }>` position: absolute; top: 0; left: 0; - border-radius: 0 0 0 ${borderRadius}; + border-radius: ${borderRadius}; background: ${props => props.isLibraryFrame ? theme.euiColorEmptyShade diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx index c9f7158f464a4a..c9f7057d2fb868 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx @@ -12,16 +12,17 @@ import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackfram import { fontFamilyCode, fontSize, px, units } from '../../../style/variables'; const FileDetails = styled.div` - color: ${theme.euiColorMediumShade}; - padding: ${px(units.half)}; + color: ${theme.euiColorDarkShade}; + padding: ${px(units.half)} 0; font-family: ${fontFamilyCode}; font-size: ${fontSize}; `; + const LibraryFrameFileDetail = styled.span` color: ${theme.euiColorDarkShade}; `; + const AppFrameFileDetail = styled.span` - font-weight: bold; color: ${theme.euiColorFullShade}; `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStackFrames.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStackFrames.tsx deleted file mode 100644 index 9f8229e024c37e..00000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStackFrames.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiLink, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { Fragment } from 'react'; -import styled from 'styled-components'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; -import { Ellipsis } from '../../shared/Icons'; -import { Stackframe } from './Stackframe'; - -const LibraryFrameToggle = styled.div` - user-select: none; -`; - -interface Props { - stackframes: IStackframe[]; - codeLanguage?: string; - initialVisiblity: boolean; -} - -interface State { - isVisible: boolean; -} - -export class LibraryStackFrames extends React.Component { - public state = { - isVisible: this.props.initialVisiblity - }; - - public onClick = () => { - this.setState(({ isVisible }) => ({ isVisible: !isVisible })); - }; - - public render() { - const { stackframes, codeLanguage } = this.props; - const { isVisible } = this.state; - if (stackframes.length === 0) { - return null; - } - - return ( -
- - - {' '} - {i18n.translate( - 'xpack.apm.stacktraceTab.libraryFramesToogleButtonLabel', - { - defaultMessage: - '{count, plural, one {# library frame} other {# library frames}}', - values: { count: stackframes.length } - } - )} - - - -
- {isVisible && ( - - - {stackframes.map((stackframe, i) => ( - - ))} - - )} -
-
- ); - } -} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx new file mode 100644 index 00000000000000..94751f9d0461d2 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { LibraryStacktrace } from './LibraryStacktrace'; + +describe('LibraryStacktrace', () => { + describe('render', () => { + describe('with no stack frames', () => { + it('renders null', () => { + const props = { id: 'testId', stackframes: [] }; + + expect(shallow().html()).toBeNull(); + }); + }); + + describe('with stack frames', () => { + it('renders an accordion', () => { + const props = { + id: 'testId', + stackframes: [{ filename: 'testFilename', line: { number: 1 } }] + }; + + expect( + shallow().find('EuiAccordion') + ).toHaveLength(1); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx new file mode 100644 index 00000000000000..f62ba954078931 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiAccordion } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import styled from 'styled-components'; +import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; +import { Stackframe } from './Stackframe'; +import { px, unit } from '../../../style/variables'; + +const FramesContainer = styled('div')` + padding-left: ${px(unit)}; +`; + +interface Props { + codeLanguage?: string; + stackframes: IStackframe[]; + id: string; +} + +export function LibraryStacktrace({ codeLanguage, id, stackframes }: Props) { + if (stackframes.length === 0) { + return null; + } + + return ( + + + {stackframes.map((stackframe, i) => ( + + ))} + + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx index cd6c7ce9224a58..a307cc56cc71a7 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import React from 'react'; import styled from 'styled-components'; +import { EuiAccordion } from '@elastic/eui'; import { IStackframe, IStackframeWithLineContext @@ -16,16 +17,11 @@ import { fontFamilyCode, fontSize } from '../../../style/variables'; -import { FrameHeading } from '../Stacktrace/FrameHeading'; +import { FrameHeading } from './FrameHeading'; import { Context } from './Context'; import { Variables } from './Variables'; -const CodeHeader = styled.div` - border-bottom: 1px solid ${theme.euiColorLightShade}; - border-radius: ${borderRadius} ${borderRadius} 0 0; -`; - -const Container = styled.div<{ isLibraryFrame: boolean }>` +const ContextContainer = styled.div<{ isLibraryFrame: boolean }>` position: relative; font-family: ${fontFamilyCode}; font-size: ${fontSize}; @@ -40,12 +36,16 @@ const Container = styled.div<{ isLibraryFrame: boolean }>` interface Props { stackframe: IStackframe; codeLanguage?: string; + id: string; + initialIsOpen?: boolean; isLibraryFrame?: boolean; } export function Stackframe({ stackframe, codeLanguage, + id, + initialIsOpen = false, isLibraryFrame = false }: Props) { if (!hasLineContext(stackframe)) { @@ -55,19 +55,22 @@ export function Stackframe({ } return ( - - + - - - - + } + id={id} + initialIsOpen={initialIsOpen} + > + + + - + ); } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index 34c46f84c76b9d..6d1b10c90a9997 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -11,11 +11,11 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { borderRadius, px, unit, units } from '../../../style/variables'; import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; -import { DottedKeyValueTable } from '../DottedKeyValueTable'; +import { KeyValueTable } from '../KeyValueTable'; +import { flattenObject } from '../../../utils/flattenObject'; const VariablesContainer = styled.div` background: ${theme.euiColorEmptyShade}; - border-top: 1px solid ${theme.euiColorLightShade}; border-radius: 0 0 ${borderRadius} ${borderRadius}; padding: ${px(units.half)} ${px(unit)}; `; @@ -24,29 +24,27 @@ interface Props { vars: IStackframe['vars']; } -export class Variables extends React.Component { - public render() { - if (!this.props.vars) { - return null; - } - - return ( - - - - - - - - - - ); +export const Variables = ({ vars }: Props) => { + if (!vars) { + return null; } -} + const flattenedVariables = flattenObject(vars); + return ( + + + + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx index b0052e8c17b64f..5f6519b7c7b968 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx @@ -16,7 +16,7 @@ describe('Stackframe', () => { let wrapper: ReactWrapper; beforeEach(() => { const stackframe = stacktracesMock[0]; - wrapper = mount(); + wrapper = mount(); }); it('should render correctly', () => { @@ -38,7 +38,7 @@ describe('Stackframe', () => { let wrapper: ReactWrapper; beforeEach(() => { const stackframe = { line: {} } as IStackframe; - wrapper = mount(); + wrapper = mount(); }); it('should render only FrameHeading', () => { @@ -55,7 +55,7 @@ describe('Stackframe', () => { it('should respect isLibraryFrame', () => { const stackframe = { line: {} } as IStackframe; const wrapper = shallow( - + ); expect(wrapper.find('FrameHeading').prop('isLibraryFrame')).toBe(true); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap index 1b465db1c6263a..55cec7389dc5c0 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap @@ -1,24 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Stackframe when stackframe has source lines should render correctly 1`] = ` -.c2 { - color: #98a2b3; - padding: 8px; +.c0 { + color: #69707d; + padding: 8px 0; font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; font-size: 14px; } -.c3 { - font-weight: bold; +.c1 { color: #000000; } -.c4 { +.c3 { position: relative; - border-radius: 0 0 4px 4px; + border-radius: 4px; } -.c5 { +.c4 { position: absolute; width: 100%; height: 18px; @@ -27,15 +26,15 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] background-color: #fef6e5; } -.c6 { +.c5 { position: absolute; top: 0; left: 0; - border-radius: 0 0 0 4px; + border-radius: 4px; background: #f5f7fa; } -.c7 { +.c6 { position: relative; min-width: 42px; padding-left: 8px; @@ -46,11 +45,11 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] border-right: 1px solid #d3dae6; } -.c7:last-of-type { +.c6:last-of-type { border-radius: 0 0 0 4px; } -.c8 { +.c7 { position: relative; min-width: 42px; padding-left: 8px; @@ -62,22 +61,22 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] background-color: #fef6e5; } -.c8:last-of-type { +.c7:last-of-type { border-radius: 0 0 0 4px; } -.c9 { +.c8 { overflow: auto; margin: 0 0 0 42px; padding: 0; background-color: #ffffff; } -.c9:last-of-type { +.c8:last-of-type { border-radius: 0 0 4px 0; } -.c10 { +.c9 { margin: 0; color: inherit; background: inherit; @@ -88,7 +87,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] line-height: 18px; } -.c11 { +.c10 { position: relative; padding: 0; margin: 0; @@ -96,12 +95,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] z-index: 2; } -.c1 { - border-bottom: 1px solid #d3dae6; - border-radius: 4px 4px 0 0; -} - -.c0 { +.c2 { position: relative; font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; font-size: 14px; @@ -111,6 +105,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] } - -
- -
- ", - "library_frame": false, - "line": Object { - "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", - "number": 307, - }, - } - } - > - -
- - - server/routes.js - - - in - - - - <anonymous> - - - at - - - line - 307 - - -
-
-
-
-
- + } + id="test" + initialIsOpen={false} + paddingSize="none" + > +
+
- -
+ - -
- - + + + + + + ", + "library_frame": false, + "line": Object { + "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", + "number": 307, + }, + } + } > -
- -
- 305 - . -
-
- -
- 306 - . -
-
- -
- 307 - . -
-
- -
- 308 - . -
-
- +
-
- 309 - . -
- -
-
- -
- - -
+                    
-                      
-                        
-                              })
-                        
-                      
-                    
-
-
- - -
+                  
+                   in
+                   
+                  
+                    
-                      
-                        
-                          
-
-                        
-                      
-                    
-
-
- - -
+                  
+                   at 
+                  
+                    
-                      
-                        
-                              client.query(
-                          
-                            'SELECT id FROM customers WHERE id=$1'
-                          
-                          , [req.body.customer_id], next())
-                        
-                      
-                    
-
-
- + +
+
+ + + +
+
+ +
+
+ +
- ", + "library_frame": false, + "line": Object { + "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", + "number": 307, + }, } } > -
-                      
-                        
+                      
+ +
+ + - req.body.lines.forEach( - - +
+ 305 + . +
+
+ +
+ 306 + . +
+
+ +
+ 307 + . +
+
+ +
+ 308 + . +
+
+ +
+ 309 + . +
+
+
+
+ +
+ - function - - ( - +
+                                  
+                                    
+                                          })
+                                    
+                                  
+                                
+ +
+ - line - - ) - - { -
- -
-
- - - -
-                      
-                        
-                                client.query(
-                          
+                                
+                                  
+                                    
+                                      
+
+                                    
+                                  
+                                
+ + + - 'SELECT id FROM products WHERE id=$1' -
- , [line.id], next()) -
-
-
-
-
-
-
+ key=" client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())2" + language="javascript" + style={ + Object { + "hljs": Object { + "background": "#fff", + "color": "black", + "display": "block", + "overflowX": "auto", + "padding": "0.5em", + }, + "hljs-addition": Object { + "backgroundColor": "#baeeba", + }, + "hljs-attr": Object { + "color": "#5c2699", + }, + "hljs-attribute": Object { + "color": "#000", + }, + "hljs-built_in": Object { + "color": "#5c2699", + }, + "hljs-builtin-name": Object { + "color": "#5c2699", + }, + "hljs-bullet": Object { + "color": "#1c00cf", + }, + "hljs-class .hljs-title": Object { + "color": "#5c2699", + }, + "hljs-comment": Object { + "color": "#006a00", + }, + "hljs-deletion": Object { + "backgroundColor": "#ffc8bd", + }, + "hljs-doctag": Object { + "fontWeight": "bold", + }, + "hljs-emphasis": Object { + "fontStyle": "italic", + }, + "hljs-formula": Object { + "backgroundColor": "#eee", + "fontStyle": "italic", + }, + "hljs-keyword": Object { + "color": "#aa0d91", + }, + "hljs-link": Object { + "color": "#080", + }, + "hljs-literal": Object { + "color": "#aa0d91", + }, + "hljs-meta": Object { + "color": "#1c00cf", + }, + "hljs-name": Object { + "color": "#008", + }, + "hljs-number": Object { + "color": "#1c00cf", + }, + "hljs-params": Object { + "color": "#5c2699", + }, + "hljs-quote": Object { + "color": "#006a00", + }, + "hljs-regexp": Object { + "color": "#080", + }, + "hljs-section": Object { + "color": "#5c2699", + }, + "hljs-selector-class": Object { + "color": "#9b703f", + }, + "hljs-selector-id": Object { + "color": "#9b703f", + }, + "hljs-selector-tag": Object { + "color": "#aa0d91", + }, + "hljs-string": Object { + "color": "#c41a16", + }, + "hljs-strong": Object { + "fontWeight": "bold", + }, + "hljs-subst": Object { + "color": "#000", + }, + "hljs-symbol": Object { + "color": "#1c00cf", + }, + "hljs-tag": Object { + "color": "#1c00cf", + }, + "hljs-template-variable": Object { + "color": "#660", + }, + "hljs-title": Object { + "color": "#1c00cf", + }, + "hljs-type": Object { + "color": "#5c2699", + }, + "hljs-variable": Object { + "color": "#660", + }, + } + } + > + +
+                                  
+                                    
+                                          client.query(
+                                      
+                                        'SELECT id FROM customers WHERE id=$1'
+                                      
+                                      , [req.body.customer_id], next())
+                                    
+                                  
+                                
+
+ + + +
+                                  
+                                    
+                                          req.body.lines.forEach(
+                                      
+                                        
+                                          function
+                                        
+                                         (
+                                        
+                                          line
+                                        
+                                        ) 
+                                      
+                                      {
+                                    
+                                  
+                                
+
+
+ + +
+                                  
+                                    
+                                            client.query(
+                                      
+                                        'SELECT id FROM products WHERE id=$1'
+                                      
+                                      , [line.id], next())
+                                    
+                                  
+                                
+
+
+
+ +
+ + +
+
+ +
-
- - + +
- + `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx index ffa740792ceded..ca14be237d22bb 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx @@ -10,7 +10,7 @@ import { isEmpty, last } from 'lodash'; import React, { Fragment } from 'react'; import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; import { EmptyMessage } from '../../shared/EmptyMessage'; -import { LibraryStackFrames } from './LibraryStackFrames'; +import { LibraryStacktrace } from './LibraryStacktrace'; import { Stackframe } from './Stackframe'; interface Props { @@ -25,7 +25,7 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { heading={i18n.translate( 'xpack.apm.stacktraceTab.noStacktraceAvailableLabel', { - defaultMessage: 'No stacktrace available.' + defaultMessage: 'No stack trace available.' } )} hideSubheading @@ -34,16 +34,17 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { } const groups = getGroupedStackframes(stackframes); + return ( {groups.map((group, i) => { // library frame - if (group.isLibraryFrame) { + if (group.isLibraryFrame && groups.length > 1) { return ( - @@ -56,7 +57,12 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { return group.stackframes.map((stackframe, idx) => ( {idx > 0 && } - + 1} + stackframe={stackframe} + /> )); })} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx index 9f07c06f6ff5aa..964debbedb2e42 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; -import { px } from '../../../../../code/public/style/variables'; +import { px } from '../../../../public/style/variables'; import { ErrorCountBadge } from '../../app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge'; import { units } from '../../../style/variables'; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx index 8f91b8cc5e2af1..b6e783a00b5d67 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx @@ -61,7 +61,7 @@ const TransactionSummary = ({ ) : null ]; - return ; + return ; }; export { TransactionSummary }; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx index c9f6afa0b36116..a7149c7604695f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx @@ -7,17 +7,56 @@ import { shallow } from 'enzyme'; import React from 'react'; import moment from 'moment-timezone'; -import { TimestampTooltip } from './index'; +import { TimestampTooltip, asAbsoluteTime } from './index'; import { mockNow } from '../../../utils/testHelpers'; +describe('asAbsoluteTime', () => { + afterAll(() => moment.tz.setDefault('')); + + it('should add a leading plus for timezones with positive UTC offset', () => { + moment.tz.setDefault('Europe/Copenhagen'); + expect(asAbsoluteTime({ time: 1559390400000, precision: 'minutes' })).toBe( + 'Jun 1, 2019, 14:00 (UTC+2)' + ); + }); + + it('should add a leading minus for timezones with negative UTC offset', () => { + moment.tz.setDefault('America/Los_Angeles'); + expect(asAbsoluteTime({ time: 1559390400000, precision: 'minutes' })).toBe( + 'Jun 1, 2019, 05:00 (UTC-7)' + ); + }); + + it('should use default UTC offset formatting when offset contains minutes', () => { + moment.tz.setDefault('Canada/Newfoundland'); + expect(asAbsoluteTime({ time: 1559390400000, precision: 'minutes' })).toBe( + 'Jun 1, 2019, 09:30 (UTC-02:30)' + ); + }); + + it('should respect DST', () => { + moment.tz.setDefault('Europe/Copenhagen'); + const timeWithDST = 1559390400000; // Jun 1, 2019 + const timeWithoutDST = 1575201600000; // Dec 1, 2019 + + expect(asAbsoluteTime({ time: timeWithDST })).toBe( + 'Jun 1, 2019, 14:00:00.000 (UTC+2)' + ); + + expect(asAbsoluteTime({ time: timeWithoutDST })).toBe( + 'Dec 1, 2019, 13:00:00.000 (UTC+1)' + ); + }); +}); + describe('TimestampTooltip', () => { - const timestamp = 1570720000123; + const timestamp = 1570720000123; // Oct 10, 2019, 08:06:40.123 (UTC-7) beforeAll(() => { // mock Date.now mockNow(1570737000000); - moment.tz.setDefault('Etc/GMT'); + moment.tz.setDefault('America/Los_Angeles'); }); afterAll(() => moment.tz.setDefault('')); @@ -26,7 +65,7 @@ describe('TimestampTooltip', () => { expect(shallow()) .toMatchInlineSnapshot(` @@ -40,7 +79,15 @@ describe('TimestampTooltip', () => { shallow() .find('EuiToolTip') .prop('content') - ).toBe('Oct 10th 2019, 15:06:40.123 (+0000 GMT)'); + ).toBe('Oct 10, 2019, 08:06:40.123 (UTC-7)'); + }); + + it('should format with precision in seconds', () => { + expect( + shallow() + .find('EuiToolTip') + .prop('content') + ).toBe('Oct 10, 2019, 08:06:40 (UTC-7)'); }); it('should format with precision in minutes', () => { @@ -48,7 +95,7 @@ describe('TimestampTooltip', () => { shallow() .find('EuiToolTip') .prop('content') - ).toBe('Oct 10th 2019, 15:06 (+0000 GMT)'); + ).toBe('Oct 10, 2019, 08:06 (UTC-7)'); }); it('should format with precision in days', () => { @@ -56,6 +103,6 @@ describe('TimestampTooltip', () => { shallow() .find('EuiToolTip') .prop('content') - ).toBe('Oct 10th 2019 (+0000 GMT)'); + ).toBe('Oct 10, 2019 (UTC-7)'); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx index 1f7fcccdd56d0e..d7ef6517c2fb83 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx @@ -12,7 +12,7 @@ interface Props { * timestamp in milliseconds */ time: number; - precision?: 'days' | 'minutes' | 'milliseconds'; + precision?: 'days' | 'minutes' | 'seconds' | 'milliseconds'; } function getPreciseTime(precision: Props['precision']) { @@ -21,17 +21,33 @@ function getPreciseTime(precision: Props['precision']) { return ''; case 'minutes': return ', HH:mm'; + case 'seconds': + return ', HH:mm:ss'; default: return ', HH:mm:ss.SSS'; } } +function withLeadingPlus(value: number) { + return value > 0 ? `+${value}` : value; +} + +export function asAbsoluteTime({ time, precision = 'milliseconds' }: Props) { + const momentTime = moment(time); + const utcOffsetHours = momentTime.utcOffset() / 60; + const utcOffsetFormatted = Number.isInteger(utcOffsetHours) + ? withLeadingPlus(utcOffsetHours) + : 'Z'; + + return momentTime.format( + `MMM D, YYYY${getPreciseTime(precision)} (UTC${utcOffsetFormatted})` + ); +} + export function TimestampTooltip({ time, precision = 'milliseconds' }: Props) { const momentTime = moment(time); const relativeTimeLabel = momentTime.fromNow(); - const absoluteTimeLabel = momentTime.format( - `MMM Do YYYY${getPreciseTime(precision)} (ZZ zz)` - ); + const absoluteTimeLabel = asAbsoluteTime({ time, precision }); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index a121e8dc25b437..533bc845f14f99 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -81,7 +81,7 @@ export const TransactionActionMenu: FunctionComponent = ( const infraConfigItems: InfraConfigItem[] = [ { - icon: 'loggingApp', + icon: 'logsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel', { defaultMessage: 'Show pod logs' } @@ -91,7 +91,7 @@ export const TransactionActionMenu: FunctionComponent = ( query: { time } }, { - icon: 'loggingApp', + icon: 'logsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel', { defaultMessage: 'Show container logs' } @@ -101,7 +101,7 @@ export const TransactionActionMenu: FunctionComponent = ( query: { time } }, { - icon: 'loggingApp', + icon: 'logsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel', { defaultMessage: 'Show host logs' } @@ -111,7 +111,7 @@ export const TransactionActionMenu: FunctionComponent = ( query: { time } }, { - icon: 'loggingApp', + icon: 'logsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel', { defaultMessage: 'Show trace logs' } @@ -124,7 +124,7 @@ export const TransactionActionMenu: FunctionComponent = ( } }, { - icon: 'infraApp', + icon: 'metricsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel', { defaultMessage: 'Show pod metrics' } @@ -134,7 +134,7 @@ export const TransactionActionMenu: FunctionComponent = ( query: infraMetricsQuery }, { - icon: 'infraApp', + icon: 'metricsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel', { defaultMessage: 'Show container metrics' } @@ -144,7 +144,7 @@ export const TransactionActionMenu: FunctionComponent = ( query: infraMetricsQuery }, { - icon: 'infraApp', + icon: 'metricsApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel', { defaultMessage: 'Show host metrics' } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/getTimezoneOffsetInMs.test.ts b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/getTimezoneOffsetInMs.test.ts index 0605c7dc8b02ac..dc815145db4ade 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/getTimezoneOffsetInMs.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/getTimezoneOffsetInMs.test.ts @@ -7,7 +7,8 @@ import { getTimezoneOffsetInMs } from './getTimezoneOffsetInMs'; import moment from 'moment-timezone'; -describe('getTimezoneOffsetInMs', () => { +// FAILING: https://github.com/elastic/kibana/issues/50005 +describe.skip('getTimezoneOffsetInMs', () => { describe('when no default timezone is set', () => { it('guesses the timezone', () => { const guess = jest.fn(() => 'Etc/UTC'); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js index 20096d81a94cb8..f5992ac7fc63b5 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js @@ -7,7 +7,6 @@ import React from 'react'; import { isEmpty } from 'lodash'; import { Hint } from 'react-vis'; -import moment from 'moment'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import { @@ -20,6 +19,7 @@ import { } from '../../../../style/variables'; import Legend from '../Legend'; import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { asAbsoluteTime } from '../../TimestampTooltip'; const TooltipElm = styled.div` margin: 0 ${px(unit)}; @@ -87,7 +87,9 @@ export default function Tooltip({ return ( -
{header || moment(x).format('MMMM Do YYYY, HH:mm:ss')}
+
+ {header || asAbsoluteTime({ time: x, precision: 'seconds' })} +
{showLegends ? ( diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts index 29a27f034be8aa..75b5e0f2a05c8a 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts @@ -8,6 +8,13 @@ import { compact, pick } from 'lodash'; import datemath from '@elastic/datemath'; import { IUrlParams } from './types'; +interface PathParams { + processorEvent?: 'error' | 'metric' | 'transaction'; + serviceName?: string; + errorGroupId?: string; + serviceNodeName?: string; +} + export function getParsedDate(rawDate?: string, opts = {}) { if (rawDate) { const parsed = datemath.parse(rawDate, opts); @@ -56,7 +63,7 @@ export function removeUndefinedProps(obj: T): Partial { return pick(obj, value => value !== undefined); } -export function getPathParams(pathname: string = '') { +export function getPathParams(pathname: string = ''): PathParams { const paths = getPathAsArray(pathname); const pageName = paths[0]; @@ -92,17 +99,15 @@ export function getPathParams(pathname: string = '') { }; case 'nodes': return { + processorEvent: 'metric', serviceName }; case 'service-map': return { - processorEvent: 'service-map', serviceName }; default: - return { - processorEvent: 'transaction' - }; + return {}; } case 'traces': diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts index febe0691d7c1ce..7972bdbcf2ee63 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts @@ -53,7 +53,8 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { refreshInterval = TIMEPICKER_DEFAULTS.refreshInterval, rangeFrom = TIMEPICKER_DEFAULTS.rangeFrom, rangeTo = TIMEPICKER_DEFAULTS.rangeTo, - environment + environment, + searchTerm } = query; const localUIFilters = pickKeys(query, ...localUIFilterNames); @@ -81,6 +82,7 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { kuery: kuery && decodeURIComponent(kuery), transactionName, transactionType, + searchTerm: toString(searchTerm), // path params processorEvent, diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts index ca4e0413aa4bfc..fc5f5dfb6e3013 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts @@ -29,4 +29,6 @@ export type IUrlParams = { page?: number; pageSize?: number; serviceNodeName?: string; + searchTerm?: string; + processorEvent?: 'transaction' | 'error' | 'metric'; } & Partial>; diff --git a/x-pack/legacy/plugins/apm/public/hooks/useKueryBarIndexPattern.ts b/x-pack/legacy/plugins/apm/public/hooks/useKueryBarIndexPattern.ts new file mode 100644 index 00000000000000..5fb9405559d560 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/hooks/useKueryBarIndexPattern.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useFetcher } from './useFetcher'; + +export function useKueryBarIndexPattern( + processorEvent: 'transaction' | 'metric' | 'error' | undefined +) { + const { data: indexPattern, status } = useFetcher( + callApmApi => { + return callApmApi({ + pathname: '/api/apm/kuery_bar_index_pattern', + forceCache: true, + params: { + query: { + processorEvent + } + } + }); + }, + [processorEvent] + ); + + return { + indexPattern, + status + }; +} diff --git a/x-pack/legacy/plugins/apm/public/index.tsx b/x-pack/legacy/plugins/apm/public/index.tsx index 16b8e9527245c4..d724a2976c8974 100644 --- a/x-pack/legacy/plugins/apm/public/index.tsx +++ b/x-pack/legacy/plugins/apm/public/index.tsx @@ -9,7 +9,6 @@ import ReactDOM from 'react-dom'; import { npStart } from 'ui/new_platform'; import 'react-vis/dist/style.css'; import 'ui/autoload/all'; -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; // @ts-ignore import { uiModules } from 'ui/modules'; diff --git a/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts index 4b65190d8ef222..b15231e89365a3 100644 --- a/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts +++ b/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts @@ -11,7 +11,6 @@ import mean from 'lodash.mean'; import { rgba } from 'polished'; import { TimeSeriesAPIResponse } from '../../server/lib/transactions/charts'; import { ApmTimeSeriesResponse } from '../../server/lib/transactions/charts/get_timeseries_data/transform'; -import { StringMap } from '../../typings/common'; import { Coordinate, RectCoordinate, @@ -192,7 +191,7 @@ function getColorByKey(keys: string[]) { const assignedColors = ['HTTP 2xx', 'HTTP 3xx', 'HTTP 4xx', 'HTTP 5xx']; const unknownKeys = difference(keys, assignedColors); - const unassignedColors: StringMap = zipObject(unknownKeys, [ + const unassignedColors: Record = zipObject(unknownKeys, [ theme.euiColorVis1, theme.euiColorVis3, theme.euiColorVis4, diff --git a/x-pack/legacy/plugins/apm/public/services/__test__/SessionStorageMock.ts b/x-pack/legacy/plugins/apm/public/services/__test__/SessionStorageMock.ts index abe44a0fb39345..83e8f020e25291 100644 --- a/x-pack/legacy/plugins/apm/public/services/__test__/SessionStorageMock.ts +++ b/x-pack/legacy/plugins/apm/public/services/__test__/SessionStorageMock.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { StringMap } from '../../../typings/common'; - export class SessionStorageMock { - private store: StringMap = {}; + private store: Record = {}; public clear() { this.store = {}; diff --git a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts index 968450e6aa796e..28b14fa722bbd4 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npStart } from 'ui/new_platform'; -import { ESFilter } from 'elasticsearch'; import { HttpServiceBase } from 'kibana/public'; import { PROCESSOR_EVENT, @@ -14,6 +12,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { getMlJobId, getMlPrefix } from '../../../common/ml_job_constants'; import { callApi } from './callApi'; +import { ESFilter } from '../../../typings/elasticsearch'; +import { createCallApmApi, APMClient } from './createCallApmApi'; interface MlResponseItem { id: string; @@ -32,7 +32,14 @@ interface StartedMLJobApiResponse { jobs: MlResponseItem[]; } -const { core } = npStart; +async function getTransactionIndices(http: HttpServiceBase) { + const callApmApi: APMClient = createCallApmApi(http); + const indices = await callApmApi({ + method: 'GET', + pathname: `/api/apm/settings/apm-indices` + }); + return indices['apm_oss.transactionIndices']; +} export async function startMLJob({ serviceName, @@ -43,9 +50,7 @@ export async function startMLJob({ transactionType: string; http: HttpServiceBase; }) { - const indexPatternName = core.injectedMetadata.getInjectedVar( - 'apmTransactionIndices' - ); + const transactionIndices = await getTransactionIndices(http); const groups = ['apm', serviceName.toLowerCase()]; const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, @@ -59,7 +64,7 @@ export async function startMLJob({ body: JSON.stringify({ prefix: getMlPrefix(serviceName, transactionType), groups, - indexPatternName, + indexPatternName: transactionIndices, startDatafeed: true, query: { bool: { diff --git a/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts b/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts index 146376665a0e69..95e283cd67709d 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts @@ -5,7 +5,6 @@ */ import { HttpServiceBase } from 'kibana/public'; -import { StringMap } from '../../../typings/common'; import { callApi } from './callApi'; export interface LicenseApiResponse { @@ -13,11 +12,11 @@ export interface LicenseApiResponse { is_active: boolean; }; features: { - beats_management?: StringMap; - graph?: StringMap; - grokdebugger?: StringMap; - index_management?: StringMap; - logstash?: StringMap; + beats_management?: Record; + graph?: Record; + grokdebugger?: Record; + index_management?: Record; + logstash?: Record; ml?: { is_available: boolean; license_type: number; @@ -25,12 +24,12 @@ export interface LicenseApiResponse { enable_links: boolean; show_links: boolean; }; - reporting?: StringMap; - rollup?: StringMap; - searchprofiler?: StringMap; - security?: StringMap; - spaces?: StringMap; - tilemap?: StringMap; + reporting?: Record; + rollup?: Record; + searchprofiler?: Record; + security?: Record; + spaces?: Record; + tilemap?: Record; watcher?: { is_available: boolean; enable_links: boolean; diff --git a/x-pack/legacy/plugins/apm/public/utils/__test__/flattenObject.test.ts b/x-pack/legacy/plugins/apm/public/utils/__test__/flattenObject.test.ts new file mode 100644 index 00000000000000..8ec826c7f3d084 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/__test__/flattenObject.test.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flattenObject } from '../flattenObject'; + +describe('FlattenObject', () => { + it('flattens multi level item', () => { + const data = { + foo: { + item1: 'value 1', + item2: { itemA: 'value 2' } + }, + bar: { + item3: { itemA: { itemAB: 'value AB' } }, + item4: 'value 4', + item5: [1], + item6: [1, 2, 3] + } + }; + + const flatten = flattenObject(data); + expect(flatten).toEqual([ + { key: 'bar.item3.itemA.itemAB', value: 'value AB' }, + { key: 'bar.item4', value: 'value 4' }, + { key: 'bar.item5', value: 1 }, + { key: 'bar.item6.0', value: 1 }, + { key: 'bar.item6.1', value: 2 }, + { key: 'bar.item6.2', value: 3 }, + { key: 'foo.item1', value: 'value 1' }, + { key: 'foo.item2.itemA', value: 'value 2' } + ]); + }); + it('returns an empty array if no valid object is provided', () => { + expect(flattenObject({})).toEqual([]); + expect(flattenObject(null)).toEqual([]); + expect(flattenObject(undefined)).toEqual([]); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts b/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts new file mode 100644 index 00000000000000..01a58ac03d0c3f --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { compact, isObject } from 'lodash'; + +export interface KeyValuePair { + key: string; + value: unknown; +} + +export const flattenObject = ( + item: Record | null | undefined, + parentKey?: string +): KeyValuePair[] => { + if (item) { + const isArrayWithSingleValue = Array.isArray(item) && item.length === 1; + return Object.keys(item) + .sort() + .reduce((acc: KeyValuePair[], key) => { + const childKey = isArrayWithSingleValue ? '' : key; + const currentKey = compact([parentKey, childKey]).join('.'); + // item[key] can be a primitive (string, number, boolean, null, undefined) or Object or Array + if (isObject(item[key])) { + return acc.concat(flattenObject(item[key], currentKey)); + } else { + acc.push({ key: currentKey, value: item[key] }); + return acc; + } + }, []); + } + return []; +}; diff --git a/x-pack/legacy/plugins/apm/public/utils/httpStatusCodeToColor.ts b/x-pack/legacy/plugins/apm/public/utils/httpStatusCodeToColor.ts index db1ed490eb7f20..130a7b52ea33be 100644 --- a/x-pack/legacy/plugins/apm/public/utils/httpStatusCodeToColor.ts +++ b/x-pack/legacy/plugins/apm/public/utils/httpStatusCodeToColor.ts @@ -5,8 +5,6 @@ */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { StringMap } from '../../typings/common'; - const { euiColorDarkShade, euiColorWarning } = theme; export const errorColor = '#c23c2b'; @@ -14,7 +12,7 @@ export const neutralColor = euiColorDarkShade; export const successColor = '#327a42'; export const warningColor = euiColorWarning; -const httpStatusCodeColors: StringMap = { +const httpStatusCodeColors: Record = { 1: neutralColor, 2: successColor, 3: neutralColor, diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 73b81bfed4794c..a18882120fe757 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -15,9 +15,9 @@ import { Moment } from 'moment-timezone'; import React from 'react'; import { render, waitForElement } from 'react-testing-library'; import { MemoryRouter } from 'react-router-dom'; -import { ESFilter } from 'elasticsearch'; import { LocationProvider } from '../context/LocationContext'; import { PromiseReturnType } from '../../typings/common'; +import { ESFilter } from '../../typings/elasticsearch'; export function toJson(wrapper: ReactWrapper) { return enzymeToJson(wrapper, { @@ -102,6 +102,15 @@ interface MockSetup { has: any; }; uiFiltersES: ESFilter[]; + indices: { + 'apm_oss.sourcemapIndices': string; + 'apm_oss.errorIndices': string; + 'apm_oss.onboardingIndices': string; + 'apm_oss.spanIndices': string; + 'apm_oss.transactionIndices': string; + 'apm_oss.metricsIndices': string; + 'apm_oss.apmAgentConfigurationIndex': string; + }; } export async function inspectSearchParams( @@ -127,7 +136,16 @@ export async function inspectSearchParams( { term: { 'service.environment': 'prod' } } - ] + ], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } }; try { await fn(mockSetup); diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index 90974cbe695880..a46b0c2895fcae 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -29,6 +29,13 @@ cd apm-integration-testing/ _Docker Compose is required_ +### Debugging Elasticsearch queries + +All APM api endpoints accept `_debug=true` as a query param that will result in the underlying ES query being outputted in the Kibana backend process. + +Example: +`/api/apm/services/my_service?_debug=true` + ### Unit testing Note: Run the following commands from `kibana/x-pack`. @@ -45,10 +52,6 @@ node scripts/jest.js plugins/apm --watch node scripts/jest.js plugins/apm --updateSnapshot ``` -### Cypress E2E tests - -See the Cypress-specific [readme.md](cypress/README.md) - ### Linting _Note: Run the following commands from `kibana/`._ @@ -65,56 +68,8 @@ yarn prettier "./x-pack/legacy/plugins/apm/**/*.{tsx,ts,js}" --write yarn eslint ./x-pack/legacy/plugins/apm --fix ``` -### Visual Studio Code - -When using [Visual Studio Code](https://code.visualstudio.com/) with APM it's best to set up a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) and add the `x-pack/legacy/plugins/apm` directory, the `x-pack` directory, and the root of the Kibana repository to the workspace. This makes it so you can navigate and search within APM and use the wider workspace roots when you need to widen your search. - -#### Using the Jest extension - -The [vscode-jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) is a good way to run your Jest tests inside the editor. - -Some of the benefits of using the extension over just running it in a terminal are: - -• It shows the pass/fail of a test inline in the test file -• It shows the error message in the test file if it fails -• You don’t have to have the terminal process running -• It can automatically update your snapshots when they change -• Coverage mapping - -The extension doesn't really work well if you're trying to use it on all of Kibana or all of X-Pack, but it works well if you configure it to run only on the files in APM. - -If you have a workspace configured as described above you should have: - -```json -"jest.disabledWorkspaceFolders": ["kibana", "x-pack"] -``` - -in your Workspace settings, and: - -```json -"jest.pathToJest": "node scripts/jest.js --testPathPattern=legacy/plugins/apm", -"jest.rootPath": "../../.." -``` - -in the settings for the APM folder. - -#### Jest debugging - -To make the [VSCode debugger](https://vscode.readthedocs.io/en/latest/editor/debugging/) work with Jest (you can set breakpoints in the code and tests and use the VSCode debugger) you'll need the [Node Debug extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.node-debug2) installed and can set up a launch configuration like: - -```json -{ - "type": "node", - "name": "APM Jest", - "request": "launch", - "args": ["--runInBand", "--testPathPattern=legacy/plugins/apm"], - "cwd": "${workspaceFolder}/../../..", - "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart", - "disableOptimisticBPs": true, - "program": "${workspaceFolder}/../../../scripts/jest.js", - "runtimeVersion": "10.15.2" -} -``` +#### Further resources -(you'll want `runtimeVersion` to match what's in the Kibana root .nvmrc. Depending on your setup, you might be able to remove this line.) +- [Cypress integration tests](cypress/README.md) +- [VSCode setup instructions](./dev_docs/vscode_setup.md) +- [Github PR commands](./dev_docs/github_commands.md) diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig.js b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig.js new file mode 100644 index 00000000000000..c1f1472dc90241 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const { optimizeTsConfig } = require('./optimize-tsconfig/optimize'); + +optimizeTsConfig(); diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/optimize.js b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/optimize.js new file mode 100644 index 00000000000000..ef9e393db3eca7 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/optimize.js @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +const fs = require('fs'); +const promisify = require('util').promisify; +const path = require('path'); +const json5 = require('json5'); +const execa = require('execa'); + +const copyFile = promisify(fs.copyFile); +const rename = promisify(fs.rename); +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); + +const { + xpackRoot, + kibanaRoot, + apmRoot, + tsconfigTpl, + filesToIgnore +} = require('./paths'); +const { unoptimizeTsConfig } = require('./unoptimize'); + +function updateParentTsConfigs() { + return Promise.all( + [ + path.resolve(xpackRoot, 'apm.tsconfig.json'), + path.resolve(kibanaRoot, 'tsconfig.json') + ].map(async filename => { + const config = json5.parse(await readFile(filename, 'utf-8')); + + await writeFile( + filename, + JSON.stringify( + { + ...config, + include: [] + }, + null, + 2 + ), + { encoding: 'utf-8' } + ); + }) + ); +} + +async function setIgnoreChanges() { + for (const filename of filesToIgnore) { + await execa('git', ['update-index', '--skip-worktree', filename]); + } +} + +const optimizeTsConfig = () => { + return unoptimizeTsConfig() + .then(() => + Promise.all([ + copyFile(tsconfigTpl, path.resolve(apmRoot, './tsconfig.json')), + rename( + path.resolve(xpackRoot, 'tsconfig.json'), + path.resolve(xpackRoot, 'apm.tsconfig.json') + ) + ]) + ) + .then(() => updateParentTsConfigs()) + .then(() => setIgnoreChanges()) + .then(() => { + // eslint-disable-next-line no-console + console.log( + 'Created an optimized tsconfig.json for APM. To undo these changes, run `./scripts/unoptimize-tsconfig.js`' + ); + }); +}; + +module.exports = { + optimizeTsConfig +}; diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/paths.js b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/paths.js new file mode 100644 index 00000000000000..cdb8e4d878ea36 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/paths.js @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +const path = require('path'); + +const apmRoot = path.resolve(__dirname, '../..'); +const xpackRoot = path.resolve(apmRoot, '../../..'); +const kibanaRoot = path.resolve(xpackRoot, '..'); + +const tsconfigTpl = path.resolve(__dirname, './tsconfig.json'); + +const filesToIgnore = [ + path.resolve(xpackRoot, 'tsconfig.json'), + path.resolve(kibanaRoot, 'tsconfig.json') +]; + +module.exports = { + apmRoot, + xpackRoot, + kibanaRoot, + tsconfigTpl, + filesToIgnore +}; diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json new file mode 100644 index 00000000000000..e7d9abea65a3ac --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../apm.tsconfig.json", + "include": [ + "./**/*", + "../../../typings/**/*" + ], + "exclude": [ + "**/__fixtures__/**/*", + "./cypress/**/*" + ] +} diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/unoptimize.js b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/unoptimize.js new file mode 100644 index 00000000000000..3fdf2a97363a8c --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/unoptimize.js @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable import/no-extraneous-dependencies */ + +const path = require('path'); +const execa = require('execa'); +const fs = require('fs'); +const promisify = require('util').promisify; +const removeFile = promisify(fs.unlink); +const exists = promisify(fs.exists); + +const { apmRoot, filesToIgnore } = require('./paths'); + +async function unoptimizeTsConfig() { + for (const filename of filesToIgnore) { + await execa('git', ['update-index', '--no-skip-worktree', filename]); + await execa('git', ['checkout', filename]); + } + + const apmTsConfig = path.join(apmRoot, 'tsconfig.json'); + if (await exists(apmTsConfig)) { + await removeFile(apmTsConfig); + } +} + +module.exports = { + unoptimizeTsConfig: () => { + return unoptimizeTsConfig().then(() => { + // eslint-disable-next-line no-console + console.log('Removed APM TypeScript optimizations'); + }); + } +}; diff --git a/x-pack/legacy/plugins/apm/scripts/unoptimize-tsconfig.js b/x-pack/legacy/plugins/apm/scripts/unoptimize-tsconfig.js new file mode 100644 index 00000000000000..5362b6a6d52e20 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/unoptimize-tsconfig.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const { unoptimizeTsConfig } = require('./optimize-tsconfig/unoptimize'); + +unoptimizeTsConfig(); diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap index 8d89e22fa98643..d336d71424750e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap @@ -50,7 +50,7 @@ Array [ }, "size": 0, }, - "index": "myIndex", + "index": "apm-*", }, ], ] diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts index 0b8cd8ae8703f8..b7081c43465bf8 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts @@ -39,7 +39,16 @@ describe('timeseriesFetcher', () => { { term: { 'service.environment': 'prod' } } - ] + ], + indices: { + 'apm_oss.sourcemapIndices': 'apm-*', + 'apm_oss.errorIndices': 'apm-*', + 'apm_oss.onboardingIndices': 'apm-*', + 'apm_oss.spanIndices': 'apm-*', + 'apm_oss.transactionIndices': 'apm-*', + 'apm_oss.metricsIndices': 'apm-*', + 'apm_oss.apmAgentConfigurationIndex': '.apm-agent-configuration' + } } }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 9c49a7f613aaa1..afb8237178a20a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, @@ -25,7 +25,7 @@ export async function getBuckets({ bucketSize: number; setup: Setup; }) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const filter: ESFilter[] = [ { term: { [PROCESSOR_EVENT]: 'error' } }, { term: { [SERVICE_NAME]: serviceName } }, @@ -38,7 +38,7 @@ export async function getBuckets({ } const params = { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts index dee5deb4064e49..caadbfef326984 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts @@ -29,10 +29,10 @@ export async function getErrorGroup({ groupId: string; setup: Setup; }) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { size: 1, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts index f692f94e255fc3..193ee4c2bbd1c2 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts @@ -17,6 +17,7 @@ import { APMError } from '../../../typings/es_schemas/ui/APMError'; import { Setup } from '../helpers/setup_request'; import { getErrorGroupsProjection } from '../../../common/projections/errors'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { SortOptions } from '../../../typings/elasticsearch/aggregations'; export type ErrorGroupListAPIResponse = PromiseReturnType< typeof getErrorGroups @@ -30,7 +31,7 @@ export async function getErrorGroups({ }: { serviceName: string; sortField?: string; - sortDirection?: string; + sortDirection?: 'asc' | 'desc'; setup: Setup; }) { const { client } = setup; @@ -40,18 +41,21 @@ export async function getErrorGroups({ const projection = getErrorGroupsProjection({ setup, serviceName }); + const order: SortOptions = sortByLatestOccurrence + ? { + max_timestamp: sortDirection + } + : { _count: sortDirection }; + const params = mergeProjection(projection, { body: { size: 0, aggs: { error_groups: { terms: { + ...projection.body.aggs.error_groups.terms, size: 500, - order: sortByLatestOccurrence - ? { - max_timestamp: sortDirection - } - : { _count: sortDirection } + order }, aggs: { sample: { @@ -64,7 +68,7 @@ export async function getErrorGroups({ ERROR_GROUP_ID, '@timestamp' ], - sort: [{ '@timestamp': 'desc' }], + sort: [{ '@timestamp': 'desc' as const }], size: 1 } }, @@ -98,13 +102,13 @@ export async function getErrorGroups({ }; } - const resp = await client.search(params); + const resp = await client.search(params); // aggregations can be undefined when no matching indices are found. // this is an exception rather than the rule so the ES type does not account for this. const hits = (idx(resp, _ => _.aggregations.error_groups.buckets) || []).map( bucket => { - const source = bucket.sample.hits.hits[0]._source as SampleError; + const source = bucket.sample.hits.hits[0]._source; const message = idx(source, _ => _.error.log.message) || idx(source, _ => _.error.exception[0].message); diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts index aaac56d16c5996..9a6aed02e2a84c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts @@ -24,10 +24,10 @@ export async function getTraceErrorsPerTransaction( traceId: string, setup: Setup ): Promise { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const params = { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts index df471af4f5ee08..0f0a11a868d6d7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { getEnvironmentUiFilterES } from '../get_environment_ui_filter_es'; import { ENVIRONMENT_NOT_DEFINED } from '../../../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames'; +import { ESFilter } from '../../../../../typings/elasticsearch'; describe('getEnvironmentUiFilterES', () => { it('should return undefined, when environment is undefined', () => { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts index 8a94e42a40b217..57feaf6a6fd0ed 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts index 4ba4caa8c69d8e..2543c2b9a8a618 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { Server } from 'hapi'; import { idx } from '@kbn/elastic-idx'; import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ISavedObject } from '../../../../public/services/rest/savedObjects'; import { StaticIndexPattern } from '../../../../../../../../src/legacy/core_plugins/data/public'; import { getAPMIndexPattern } from '../../../lib/index_pattern'; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts index 1658cb07e4d2f6..bea7e510875db3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts @@ -5,7 +5,7 @@ */ import { Server } from 'hapi'; -import { ESFilter } from 'elasticsearch'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { UIFilters } from '../../../../typings/ui-filters'; import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; import { getKueryUiFilterES } from './get_kuery_ui_filter_es'; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts index 79fcfe95c05528..ee41599454dd69 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts @@ -9,28 +9,20 @@ import { SearchParams, IndexDocumentParams, IndicesDeleteParams, - IndicesCreateParams, - AggregationSearchResponseWithTotalHitsAsObject + IndicesCreateParams } from 'elasticsearch'; import { Legacy } from 'kibana'; -import { cloneDeep, has, isString, set } from 'lodash'; +import { cloneDeep, has, isString, set, pick } from 'lodash'; import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames'; -import { StringMap } from '../../../typings/common'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; +import { + ESSearchResponse, + ESSearchRequest +} from '../../../typings/elasticsearch'; // `type` was deprecated in 7.0 export type APMIndexDocumentParams = Omit, 'type'>; -function getApmIndices(config: Legacy.KibanaConfig) { - return [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.onboardingIndices'), - config.get('apm_oss.sourcemapIndices'), - config.get('apm_oss.spanIndices'), - config.get('apm_oss.transactionIndices') - ]; -} - export function isApmIndex( apmIndices: string[], indexParam: SearchParams['index'] @@ -73,10 +65,23 @@ async function getParamsForSearchRequest( params: SearchParams, apmOptions?: APMOptions ) { - const config = req.server.config(); const uiSettings = req.getUiSettingsService(); - const apmIndices = getApmIndices(config); - const includeFrozen = await uiSettings.get('search:includeFrozen'); + const [indices, includeFrozen] = await Promise.all([ + getApmIndices(req.server), + uiSettings.get('search:includeFrozen') + ]); + + // Get indices for legacy data filter (only those which apply) + const apmIndices: string[] = Object.values( + pick(indices, [ + 'apm_oss.sourcemapIndices', + 'apm_oss.errorIndices', + 'apm_oss.onboardingIndices', + 'apm_oss.spanIndices', + 'apm_oss.transactionIndices', + 'apm_oss.metricsIndices' + ]) + ); return { ...addFilterForLegacyData(apmIndices, params, apmOptions), // filter out pre-7.0 data ignore_throttled: !includeFrozen // whether to query frozen indices or not @@ -89,13 +94,16 @@ interface APMOptions { export function getESClient(req: Legacy.Request) { const cluster = req.server.plugins.elasticsearch.getCluster('data'); - const query = req.query as StringMap; + const query = req.query as Record; return { - search: async ( - params: U, + search: async < + TDocument = unknown, + TSearchRequest extends ESSearchRequest = {} + >( + params: TSearchRequest, apmOptions?: APMOptions - ): Promise> => { + ): Promise> => { const nextParams = await getParamsForSearchRequest( req, params, @@ -117,9 +125,7 @@ export function getESClient(req: Legacy.Request) { req, 'search', nextParams - ) as unknown) as Promise< - AggregationSearchResponseWithTotalHitsAsObject - >; + ) as unknown) as Promise>; }, index: (params: APMIndexDocumentParams) => { return cluster.callWithRequest(req, 'index', params); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts index 3070d7b00b6843..81dd8b34c88472 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts @@ -6,10 +6,10 @@ import { Server } from 'hapi'; -export function getSavedObjectsClient(server: Server) { +export function getSavedObjectsClient(server: Server, clusterName = 'admin') { const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( - 'admin' + clusterName ); const internalRepository = getSavedObjectsRepository(callWithInternalUser); return new SavedObjectsClient(internalRepository); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index cfc33b4fad7ea3..57de438be7f2ad 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -3,11 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { Legacy } from 'kibana'; import { setupRequest } from './setup_request'; import { uiSettingsServiceMock } from 'src/core/server/mocks'; +jest.mock('../settings/apm_indices/get_apm_indices', () => ({ + getApmIndices: async () => ({ + 'apm_oss.sourcemapIndices': 'apm-*', + 'apm_oss.errorIndices': 'apm-*', + 'apm_oss.onboardingIndices': 'apm-*', + 'apm_oss.spanIndices': 'apm-*', + 'apm_oss.transactionIndices': 'apm-*', + 'apm_oss.metricsIndices': 'apm-*', + 'apm_oss.apmAgentConfigurationIndex': 'apm-*' + }) +})); + function getMockRequest() { const callWithRequestSpy = jest.fn(); const mockRequest = ({ @@ -31,7 +42,7 @@ describe('setupRequest', () => { it('should call callWithRequest with default args', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); const { client } = await setupRequest(mockRequest); - await client.search({ index: 'apm-*', body: { foo: 'bar' } }); + await client.search({ index: 'apm-*', body: { foo: 'bar' } } as any); expect(callWithRequestSpy).toHaveBeenCalledWith(mockRequest, 'search', { index: 'apm-*', body: { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts index 4054537e04b40f..3ec519d5e71b57 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts @@ -10,6 +10,7 @@ import moment from 'moment'; import { getESClient } from './es_client'; import { getUiFiltersES } from './convert_ui_filters/get_ui_filters_es'; import { PromiseReturnType } from '../../../typings/common'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; function decodeUiFilters(server: Server, uiFiltersEncoded?: string) { if (!uiFiltersEncoded) { @@ -31,12 +32,17 @@ export async function setupRequest(req: Legacy.Request) { const query = (req.query as unknown) as APMRequestQuery; const { server } = req; const config = server.config(); + const [uiFiltersES, indices] = await Promise.all([ + decodeUiFilters(server, query.uiFilters), + getApmIndices(server) + ]); return { start: moment.utc(query.start).valueOf(), end: moment.utc(query.end).valueOf(), - uiFiltersES: await decodeUiFilters(server, query.uiFilters), + uiFiltersES, client: getESClient(req), - config + config, + indices }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/getKueryBarIndexPattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/getKueryBarIndexPattern.ts new file mode 100644 index 00000000000000..a5ba4573cfd58a --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/getKueryBarIndexPattern.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Legacy } from 'kibana'; +import { StaticIndexPattern } from 'ui/index_patterns'; +import { APICaller } from 'src/core/server'; +import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server'; +import { Setup } from '../helpers/setup_request'; + +export const getKueryBarIndexPattern = async ({ + request, + processorEvent, + setup +}: { + request: Legacy.Request; + processorEvent?: 'transaction' | 'error' | 'metric'; + setup: Setup; +}) => { + const { indices } = setup; + + const indexPatternsFetcher = new IndexPatternsFetcher( + (...rest: Parameters) => + request.server.plugins.elasticsearch + .getCluster('data') + .callWithRequest(request, ...rest) + ); + + const indexNames = processorEvent + ? [processorEvent] + : ['transaction' as const, 'metric' as const, 'error' as const]; + + const indicesMap = { + transaction: indices['apm_oss.transactionIndices'], + metric: indices['apm_oss.metricsIndices'], + error: indices['apm_oss.errorIndices'] + }; + + const configuredIndices = indexNames.map(name => indicesMap[name]); + + const fields = await indexPatternsFetcher.getFieldsForWildcard({ + pattern: configuredIndices + }); + + const pattern: StaticIndexPattern = { + fields, + title: configuredIndices.join(',') + }; + + return pattern; +}; diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts index 7b8fc4ac44f644..f785e450628079 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts @@ -124,7 +124,7 @@ export async function fetchAndTransformGcMetrics({ } const series = aggregations.per_pool.buckets.map((poolBucket, i) => { - const label = poolBucket.key; + const label = poolBucket.key as string; const timeseriesData = poolBucket.over_time; const data = (idx(timeseriesData, _ => _.buckets) || []).map(bucket => { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index 9837fb21b6da0f..3d425e50bc60af 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -4,20 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Unionize } from 'utility-types'; import { Setup } from '../helpers/setup_request'; import { getMetricsDateHistogramParams } from '../helpers/metrics'; import { ChartBase } from './types'; import { transformDataToMetricsChart } from './transform_metrics_chart'; import { getMetricsProjection } from '../../../common/projections/metrics'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; interface Aggs { - [key: string]: { - min?: any; - max?: any; - sum?: any; - avg?: any; - }; + [key: string]: Unionize<{ + min: AggregationOptionsByType['min']; + max: AggregationOptionsByType['max']; + sum: AggregationOptionsByType['sum']; + avg: AggregationOptionsByType['avg']; + }>; } interface Filter { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 89bc3b4107a5ff..594a0d35ed176c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -4,12 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { - AggregationSearchResponseWithTotalHitsAsObject, - AggregatedValue -} from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; +import { Unionize, Overwrite } from 'utility-types'; import { ChartBase } from './types'; +import { + ESSearchResponse, + ESSearchRequest +} from '../../../typings/elasticsearch'; +import { AggregationOptionsByType } from '../../../typings/elasticsearch/aggregations'; const colors = [ theme.euiColorVis0, @@ -25,33 +27,30 @@ export type GenericMetricsChart = ReturnType< typeof transformDataToMetricsChart >; -interface AggregatedParams { - body: { - aggs: { - timeseriesData: { - date_histogram: any; - aggs: { - min?: any; - max?: any; - sum?: any; - avg?: any; +interface MetricsAggregationMap { + min: AggregationOptionsByType['min']; + max: AggregationOptionsByType['max']; + sum: AggregationOptionsByType['sum']; + avg: AggregationOptionsByType['avg']; +} + +type GenericMetricsRequest = Overwrite< + ESSearchRequest, + { + body: { + aggs: { + timeseriesData: { + date_histogram: AggregationOptionsByType['date_histogram']; + aggs: Record>; }; - }; - } & { - [key: string]: { - min?: any; - max?: any; - sum?: any; - avg?: any; - }; + } & Record>; }; - }; -} + } +>; -export function transformDataToMetricsChart( - result: AggregationSearchResponseWithTotalHitsAsObject, - chartBase: ChartBase -) { +export function transformDataToMetricsChart< + TRequest extends GenericMetricsRequest +>(result: ESSearchResponse, chartBase: ChartBase) { const { aggregations, hits } = result; const timeseriesData = idx(aggregations, _ => _.timeseriesData); @@ -70,7 +69,7 @@ export function transformDataToMetricsChart( color: chartBase.series[seriesKey].color || colors[i], overallValue, data: (idx(timeseriesData, _ => _.buckets) || []).map(bucket => { - const { value } = bucket[seriesKey] as AggregatedValue; + const { value } = bucket[seriesKey] as { value: number | null }; const y = value === null || isNaN(value) ? null : value; return { x: bucket.key, diff --git a/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts b/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts index afdf4795c4d297..1e415252200ce4 100644 --- a/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts @@ -24,45 +24,45 @@ const getServiceNodes = async ({ }) => { const { client } = setup; - const projection = mergeProjection( - getServiceNodesProjection({ setup, serviceName }), - { - body: { - aggs: { - nodes: { - terms: { - size: 10000, - missing: SERVICE_NODE_NAME_MISSING + const projection = getServiceNodesProjection({ setup, serviceName }); + + const params = mergeProjection(projection, { + body: { + aggs: { + nodes: { + terms: { + ...projection.body.aggs.nodes.terms, + size: 10000, + missing: SERVICE_NODE_NAME_MISSING + }, + aggs: { + cpu: { + avg: { + field: METRIC_PROCESS_CPU_PERCENT + } + }, + heapMemory: { + avg: { + field: METRIC_JAVA_HEAP_MEMORY_USED + } }, - aggs: { - cpu: { - avg: { - field: METRIC_PROCESS_CPU_PERCENT - } - }, - heapMemory: { - avg: { - field: METRIC_JAVA_HEAP_MEMORY_USED - } - }, - nonHeapMemory: { - avg: { - field: METRIC_JAVA_NON_HEAP_MEMORY_USED - } - }, - threadCount: { - max: { - field: METRIC_JAVA_THREAD_COUNT - } + nonHeapMemory: { + avg: { + field: METRIC_JAVA_NON_HEAP_MEMORY_USED + } + }, + threadCount: { + max: { + field: METRIC_JAVA_THREAD_COUNT } } } } } } - ); + }); - const response = await client.search(projection); + const response = await client.search(params); if (!response.aggregations) { return []; @@ -70,7 +70,7 @@ const getServiceNodes = async ({ return response.aggregations.nodes.buckets.map(bucket => { return { - name: bucket.key, + name: bucket.key as string, cpu: bucket.cpu.value, heapMemory: bucket.heapMemory.value, nonHeapMemory: bucket.nonHeapMemory.value, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 71217ccc36f7b6..acd5dc119b737c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -56,9 +56,7 @@ Object { }, "size": 0, }, - "index": Array [ - "myIndex", - ], + "index": "myIndex", "terminateAfter": 1, } `; @@ -228,8 +226,6 @@ Object { }, "size": 0, }, - "index": Array [ - "myIndex", - ], + "index": "myIndex", } `; diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts index 3c6f6843899702..bbb18eae7eb091 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -13,14 +13,14 @@ import { rangeFilter } from '../helpers/range_filter'; import { Setup } from '../helpers/setup_request'; export async function getServiceAgentName(serviceName: string, setup: Setup) { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const params = { terminateAfter: 1, index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices'), - config.get('apm_oss.metricsIndices') + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'], + indices['apm_oss.metricsIndices'] ], body: { size: 0, @@ -44,6 +44,8 @@ export async function getServiceAgentName(serviceName: string, setup: Setup) { }; const { aggregations } = await client.search(params); - const agentName = idx(aggregations, _ => _.agents.buckets[0].key); + const agentName = idx(aggregations, _ => _.agents.buckets[0].key) as + | string + | undefined; return { agentName }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts index a1f035da9dc1ac..00ffa7484bb48f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -16,10 +16,10 @@ export async function getServiceTransactionTypes( serviceName: string, setup: Setup ) { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const params = { - index: [config.get('apm_oss.transactionIndices')], + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { @@ -41,6 +41,6 @@ export async function getServiceTransactionTypes( const { aggregations } = await client.search(params); const buckets = idx(aggregations, _ => _.types.buckets) || []; - const transactionTypes = buckets.map(bucket => bucket.key); + const transactionTypes = buckets.map(bucket => bucket.key as string); return { transactionTypes }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts index d1654632dbb262..6c28d19a147119 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts @@ -4,21 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { PROCESSOR_EVENT } from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; // Note: this logic is duplicated in tutorials/apm/envs/on_prem export async function getAgentStatus(setup: Setup) { - const { client, config } = setup; + const { client, indices } = setup; - const params: SearchParams = { + const params = { terminateAfter: 1, index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.sourcemapIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.errorIndices'], + indices['apm_oss.metricsIndices'], + indices['apm_oss.sourcemapIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts index 1379d79326adde..5497eaa13d34b1 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts @@ -12,11 +12,11 @@ import { Setup } from '../../helpers/setup_request'; // returns true if 6.x data is found export async function getLegacyDataStatus(setup: Setup) { - const { client, config } = setup; + const { client, indices } = setup; const params = { terminateAfter: 1, - index: [config.get('apm_oss.transactionIndices')], + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts index c50506db1faecf..60f3091567059a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -28,6 +28,7 @@ export async function getServicesItems(setup: Setup) { aggs: { services: { terms: { + ...projection.body.aggs.services.terms, size: 500 }, aggs: { @@ -69,12 +70,14 @@ export async function getServicesItems(setup: Setup) { const environmentsBuckets = bucket.environments.buckets; const environments = environmentsBuckets.map( - environmentBucket => environmentBucket.key + environmentBucket => environmentBucket.key as string ); return { - serviceName: bucket.key, - agentName: idx(bucket, _ => _.agents.buckets[0].key), + serviceName: bucket.key as string, + agentName: idx(bucket, _ => _.agents.buckets[0].key) as + | string + | undefined, transactionsPerMinute, errorsPerMinute, avgResponseTime: bucket.avg.value, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index 4226c073b1de02..861732ee039232 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -6,15 +6,15 @@ import { InternalCoreSetup } from 'src/core/server'; import { CallCluster } from '../../../../../../../../src/legacy/core_plugins/elasticsearch'; +import { getApmIndices } from '../apm_indices/get_apm_indices'; export async function createApmAgentConfigurationIndex( core: InternalCoreSetup ) { try { const { server } = core.http; - const index = server - .config() - .get('apm_oss.apmAgentConfigurationIndex'); + const indices = await getApmIndices(server); + const index = indices['apm_oss.apmAgentConfigurationIndex']; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( 'admin' ); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts index 9f0d8e2f1c7183..25a4f5141498f4 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts @@ -21,11 +21,11 @@ export async function createOrUpdateConfiguration({ >; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params: APMIndexDocumentParams = { refresh: true, - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], body: { agent_name: configuration.agent_name, service: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts index 82b192125a5cff..896363c054ba78 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts @@ -13,11 +13,11 @@ export async function deleteConfiguration({ configurationId: string; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { refresh: 'wait_for', - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], id: configurationId }; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts index a18873e86c5a8e..21663b813f01f6 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts @@ -19,14 +19,14 @@ export async function getAgentNameByService({ serviceName: string; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { terminateAfter: 1, index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, @@ -49,6 +49,8 @@ export async function getAgentNameByService({ }; const { aggregations } = await client.search(params); - const agentName = idx(aggregations, _ => _.agent_names.buckets[0].key); + const agentName = idx(aggregations, _ => _.agent_names.buckets[0].key) as + | string + | undefined; return { agentName }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts index 76ebf75aada290..8215e2b9fd6686 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts @@ -20,7 +20,7 @@ export async function getAllEnvironments({ serviceName: string | undefined; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; // omit filter for service.name if "All" option is selected const serviceNameFilter = serviceName @@ -29,9 +29,9 @@ export async function getAllEnvironments({ const params = { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, @@ -58,6 +58,6 @@ export async function getAllEnvironments({ const resp = await client.search(params); const buckets = idx(resp.aggregations, _ => _.environments.buckets) || []; - const environments = buckets.map(bucket => bucket.key); + const environments = buckets.map(bucket => bucket.key as string); return [ALL_OPTION_VALUE, ...environments]; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index 120cc62cc3bc99..d5aa389cea3357 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -19,14 +19,14 @@ export async function getExistingEnvironmentsForService({ serviceName: string | undefined; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const bool = serviceName ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] } : { must_not: [{ exists: { field: SERVICE_NAME } }] }; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], body: { size: 0, query: { bool }, @@ -44,5 +44,5 @@ export async function getExistingEnvironmentsForService({ const resp = await client.search(params); const buckets = idx(resp.aggregations, _ => _.environments.buckets) || []; - return buckets.map(bucket => bucket.key); + return buckets.map(bucket => bucket.key as string); } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts index 55af96acbc7192..51a4564f535763 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts @@ -17,13 +17,13 @@ export type AgentConfigurationServicesAPIResponse = PromiseReturnType< typeof getServiceNames >; export async function getServiceNames({ setup }: { setup: Setup }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, @@ -47,6 +47,6 @@ export async function getServiceNames({ setup }: { setup: Setup }) { const resp = await client.search(params); const buckets = idx(resp.aggregations, _ => _.services.buckets) || []; - const serviceNames = buckets.map(bucket => bucket.key).sort(); + const serviceNames = buckets.map(bucket => bucket.key as string).sort(); return [ALL_OPTION_VALUE, ...serviceNames]; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts index 9edceb86dda8db..283f30b51441d3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts @@ -12,10 +12,10 @@ export type AgentConfigurationListAPIResponse = PromiseReturnType< typeof listConfigurations >; export async function listConfigurations({ setup }: { setup: Setup }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex') + index: indices['apm_oss.apmAgentConfigurationIndex'] }; const resp = await client.search(params); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts index 867045142cea06..e5349edb67f308 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts @@ -16,10 +16,10 @@ export async function markAppliedByAgent({ body: AgentConfiguration; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], id, // by specifying the `id` elasticsearch will do an "upsert" body: { ...body, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts index e8db37891e7ae5..400bd0207771af 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts @@ -15,7 +15,18 @@ describe('search configurations', () => { environment: 'production', setup: ({ config: { get: () => '' }, - client: { search: async () => searchMocks } + client: { search: async () => searchMocks }, + indices: { + apm_oss: { + sourcemapIndices: 'myIndex', + errorIndices: 'myIndex', + onboardingIndices: 'myIndex', + spanIndices: 'myIndex', + transactionIndices: 'myIndex', + metricsIndices: 'myIndex', + apmAgentConfigurationIndex: 'myIndex' + } + } } as unknown) as Setup }); @@ -29,7 +40,18 @@ describe('search configurations', () => { environment: 'production', setup: ({ config: { get: () => '' }, - client: { search: async () => searchMocks } + client: { search: async () => searchMocks }, + indices: { + apm_oss: { + sourcemapIndices: 'myIndex', + errorIndices: 'myIndex', + onboardingIndices: 'myIndex', + spanIndices: 'myIndex', + transactionIndices: 'myIndex', + metricsIndices: 'myIndex', + apmAgentConfigurationIndex: 'myIndex' + } + } } as unknown) as Setup }); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts index 664bcb9325472e..35d76d745cf4f1 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { ESSearchHit } from 'elasticsearch'; +import { ESSearchHit } from '../../../../typings/elasticsearch'; import { SERVICE_NAME, SERVICE_ENVIRONMENT @@ -21,7 +20,7 @@ export async function searchConfigurations({ environment?: string; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; // sorting order // 1. exact match: service.name AND service.environment (eg. opbeans-node / production) @@ -34,7 +33,7 @@ export async function searchConfigurations({ : []; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], body: { query: { bool: { @@ -50,7 +49,7 @@ export async function searchConfigurations({ } }; - const resp = await client.search(params); + const resp = await client.search(params); const { hits } = resp.hits; const exactMatch = hits.find( diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts new file mode 100644 index 00000000000000..cd237a52640990 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { merge } from 'lodash'; +import { KibanaConfig } from 'src/legacy/server/kbn_server'; +import { getSavedObjectsClient } from '../../helpers/saved_objects_client'; +import { Setup } from '../../helpers/setup_request'; +import { PromiseReturnType } from '../../../../typings/common'; + +export interface ApmIndicesConfig { + 'apm_oss.sourcemapIndices': string; + 'apm_oss.errorIndices': string; + 'apm_oss.onboardingIndices': string; + 'apm_oss.spanIndices': string; + 'apm_oss.transactionIndices': string; + 'apm_oss.metricsIndices': string; + 'apm_oss.apmAgentConfigurationIndex': string; +} + +export type ApmIndicesName = keyof ApmIndicesConfig; + +export const APM_INDICES_SAVED_OBJECT_TYPE = 'apm-indices'; +export const APM_INDICES_SAVED_OBJECT_ID = 'apm-indices'; + +async function getApmIndicesSavedObject(server: Server) { + const savedObjectsClient = getSavedObjectsClient(server, 'data'); + const apmIndices = await savedObjectsClient.get>( + APM_INDICES_SAVED_OBJECT_TYPE, + APM_INDICES_SAVED_OBJECT_ID + ); + return apmIndices.attributes; +} + +function getApmIndicesConfig(config: KibanaConfig): ApmIndicesConfig { + return { + 'apm_oss.sourcemapIndices': config.get('apm_oss.sourcemapIndices'), + 'apm_oss.errorIndices': config.get('apm_oss.errorIndices'), + 'apm_oss.onboardingIndices': config.get( + 'apm_oss.onboardingIndices' + ), + 'apm_oss.spanIndices': config.get('apm_oss.spanIndices'), + 'apm_oss.transactionIndices': config.get( + 'apm_oss.transactionIndices' + ), + 'apm_oss.metricsIndices': config.get('apm_oss.metricsIndices'), + 'apm_oss.apmAgentConfigurationIndex': config.get( + 'apm_oss.apmAgentConfigurationIndex' + ) + }; +} + +export async function getApmIndices(server: Server) { + try { + const apmIndicesSavedObject = await getApmIndicesSavedObject(server); + const apmIndicesConfig = getApmIndicesConfig(server.config()); + return merge({}, apmIndicesConfig, apmIndicesSavedObject); + } catch (error) { + return getApmIndicesConfig(server.config()); + } +} + +const APM_UI_INDICES: ApmIndicesName[] = [ + 'apm_oss.sourcemapIndices', + 'apm_oss.errorIndices', + 'apm_oss.onboardingIndices', + 'apm_oss.spanIndices', + 'apm_oss.transactionIndices', + 'apm_oss.metricsIndices', + 'apm_oss.apmAgentConfigurationIndex' +]; + +export async function getApmIndexSettings({ + setup, + server +}: { + setup: Setup; + server: Server; +}) { + const { config } = setup; + let apmIndicesSavedObject: PromiseReturnType; + try { + apmIndicesSavedObject = await getApmIndicesSavedObject(server); + } catch (error) { + if (error.output && error.output.statusCode === 404) { + apmIndicesSavedObject = {}; + } else { + throw error; + } + } + const apmIndicesConfig = getApmIndicesConfig(config); + + return APM_UI_INDICES.map(configurationName => ({ + configurationName, + defaultValue: apmIndicesConfig[configurationName], // value defined in kibana[.dev].yml + savedValue: apmIndicesSavedObject[configurationName] // value saved via Saved Objects service + })); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts new file mode 100644 index 00000000000000..8de47c5c44144f --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { getSavedObjectsClient } from '../../helpers/saved_objects_client'; +import { + ApmIndicesConfig, + APM_INDICES_SAVED_OBJECT_TYPE, + APM_INDICES_SAVED_OBJECT_ID +} from './get_apm_indices'; + +export async function saveApmIndices( + server: Server, + apmIndicesSavedObject: Partial +) { + const savedObjectsClient = getSavedObjectsClient(server, 'data'); + return await savedObjectsClient.create( + APM_INDICES_SAVED_OBJECT_TYPE, + apmIndicesSavedObject, + { + id: APM_INDICES_SAVED_OBJECT_ID, + overwrite: true + } + ); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts index 74e16424b10989..0df5cc016431d6 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { PROCESSOR_EVENT, TRACE_ID, @@ -18,13 +17,13 @@ import { rangeFilter } from '../helpers/range_filter'; import { Setup } from '../helpers/setup_request'; export async function getTraceItems(traceId: string, setup: Setup) { - const { start, end, client, config } = setup; + const { start, end, client, config, indices } = setup; const maxTraceItems = config.get('xpack.apm.ui.maxTraceItems'); - const params: SearchParams = { + const params = { index: [ - config.get('apm_oss.spanIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.spanIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: maxTraceItems, @@ -41,9 +40,9 @@ export async function getTraceItems(traceId: string, setup: Setup) { } }, sort: [ - { _score: { order: 'asc' } }, - { [TRANSACTION_DURATION]: { order: 'desc' } }, - { [SPAN_DURATION]: { order: 'desc' } } + { _score: { order: 'asc' as const } }, + { [TRANSACTION_DURATION]: { order: 'desc' as const } }, + { [SPAN_DURATION]: { order: 'desc' as const } } ], track_total_hits: true } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts index fc7b1b4127892f..99553690359cf5 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts @@ -16,15 +16,22 @@ function getSetup() { config: { get: jest.fn((key: string) => { switch (key) { - case 'apm_oss.transactionIndices': - return 'myIndex'; case 'xpack.apm.ui.transactionGroupBucketSize': return 100; } }), has: () => true }, - uiFiltersES: [{ term: { 'service.environment': 'test' } }] + uiFiltersES: [{ term: { 'service.environment': 'test' } }], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts index a647bd2faff363..bfa46abcad36fd 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -12,6 +12,8 @@ import { PromiseReturnType } from '../../../typings/common'; import { Setup } from '../helpers/setup_request'; import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { SortOptions } from '../../../typings/elasticsearch/aggregations'; +import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; interface TopTransactionOptions { type: 'top_transactions'; @@ -36,6 +38,11 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { options }); + const sort: SortOptions = [ + { _score: 'desc' as const }, // sort by _score to ensure that buckets with sampled:true ends up on top + { '@timestamp': { order: 'desc' as const } } + ]; + const params = mergeProjection(projection, { body: { size: 0, @@ -48,17 +55,15 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { aggs: { transactions: { terms: { - order: { sum: 'desc' }, + ...projection.body.aggs.transactions.terms, + order: { sum: 'desc' as const }, size: config.get('xpack.apm.ui.transactionGroupBucketSize') }, aggs: { sample: { top_hits: { size: 1, - sort: [ - { _score: 'desc' }, // sort by _score to ensure that buckets with sampled:true ends up on top - { '@timestamp': { order: 'desc' } } - ] + sort } }, avg: { avg: { field: TRANSACTION_DURATION } }, @@ -72,5 +77,5 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { } }); - return client.search(params); + return client.search(params); } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts index b4e70d260f512c..3ec64be08d1173 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts @@ -6,7 +6,6 @@ import moment from 'moment'; import { idx } from '@kbn/elastic-idx'; -import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { ESResponse } from './fetcher'; function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { @@ -34,10 +33,10 @@ function getTransactionGroup( const averageResponseTime = bucket.avg.value; const transactionsPerMinute = bucket.doc_count / minutes; const impact = bucket.sum.value; - const sample = bucket.sample.hits.hits[0]._source as Transaction; + const sample = bucket.sample.hits.hits[0]._source; return { - name: bucket.key, + name: bucket.key as string, sample, p95: bucket.p95.values['95.0'], averageResponseTime, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts index c440ee9c1ecbee..e092942a25ba65 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts @@ -21,9 +21,9 @@ export async function getTransactionAvgDurationByCountry({ setup: Setup; serviceName: string; }) { - const { uiFiltersES, client, config, start, end } = setup; + const { uiFiltersES, client, start, end, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { @@ -63,7 +63,7 @@ export async function getTransactionAvgDurationByCountry({ const buckets = resp.aggregations.country_code.buckets; const avgDurationsByCountry = buckets.map( ({ key, doc_count, avg_duration: { value } }) => ({ - key, + key: key as string, docCount: doc_count, value: value === null ? 0 : value }) diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts index e707ce3eafe18a..67816d67a29a2a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts @@ -9,6 +9,16 @@ import * as constants from './constants'; import noDataResponse from './mock-responses/noData.json'; import dataResponse from './mock-responses/data.json'; +const mockIndices = { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' +}; + describe('getTransactionBreakdown', () => { it('returns an empty array if no data is available', async () => { const clientSpy = jest.fn().mockReturnValueOnce(noDataResponse); @@ -24,7 +34,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -47,7 +58,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -87,7 +99,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -126,7 +139,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -149,7 +163,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts index b3c1c6603f3157..a21c4f38ac30cc 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -32,7 +32,7 @@ export async function getTransactionBreakdown({ transactionName?: string; transactionType: string; }) { - const { uiFiltersES, client, config, start, end } = setup; + const { uiFiltersES, client, start, end, indices } = setup; const subAggs = { sum_all_self_times: { @@ -50,7 +50,7 @@ export async function getTransactionBreakdown({ field: SPAN_TYPE, size: 20, order: { - _count: 'desc' + _count: 'desc' as const } }, aggs: { @@ -60,7 +60,7 @@ export async function getTransactionBreakdown({ missing: '', size: 20, order: { - _count: 'desc' + _count: 'desc' as const } }, aggs: { @@ -88,7 +88,7 @@ export async function getTransactionBreakdown({ } const params = { - index: config.get('apm_oss.metricsIndices'), + index: indices['apm_oss.metricsIndices'], body: { size: 0, query: { @@ -117,11 +117,11 @@ export async function getTransactionBreakdown({ const breakdowns = flatten( aggs.types.buckets.map(bucket => { - const type = bucket.key; + const type = bucket.key as string; return bucket.subtypes.buckets.map(subBucket => { return { - name: subBucket.key || type, + name: (subBucket.key as string) || type, percentage: (subBucket.total_self_time_per_subtype.value || 0) / sumAllSelfTimes diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts index 293cede80cbfc9..27cff20b7ff376 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts @@ -49,7 +49,7 @@ export async function getMlBucketSize({ }; try { - const resp = await client.search(params); + const resp = await client.search(params); return idx(resp, _ => _.hits.hits[0]._source.bucket_span) || 0; } catch (err) { const isHttpError = 'statusCode' in err; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts index 07f1d96618198b..cddc66e52cf701 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts @@ -30,7 +30,16 @@ describe('getAnomalySeries', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } } }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts index dc568d653a5eef..5056a100de3ce6 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts @@ -29,7 +29,16 @@ describe('timeseriesFetcher', () => { { term: { 'service.environment': 'test' } } - ] + ], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } } }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts index dc5cb925b72023..0d9cccb3b56d35 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; +import { ESFilter } from '../../../../../typings/elasticsearch'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -30,7 +30,7 @@ export function timeseriesFetcher({ transactionName: string | undefined; setup: Setup; }) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const { intervalString } = getBucketSize(start, end, 'auto'); const filter: ESFilter[] = [ @@ -50,7 +50,7 @@ export function timeseriesFetcher({ } const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { bool: { filter } }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index f403c64a94aea5..42828367f79415 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -57,7 +57,8 @@ export function getTpmBuckets( }); // Handle empty string result keys - const key = resultKey === '' ? NOT_AVAILABLE_LABEL : resultKey; + const key = + resultKey === '' ? NOT_AVAILABLE_LABEL : (resultKey as string); return { key, dataPoints }; } @@ -65,7 +66,7 @@ export function getTpmBuckets( return sortBy( buckets, - bucket => bucket.key.replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top + bucket => bucket.key.toString().replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top ); } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index 458aad225fd941..90d9a925a1f36d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -17,7 +18,7 @@ import { import { rangeFilter } from '../../../helpers/range_filter'; import { Setup } from '../../../helpers/setup_request'; -export function bucketFetcher( +export async function bucketFetcher( serviceName: string, transactionName: string, transactionType: string, @@ -27,10 +28,10 @@ export function bucketFetcher( bucketSize: number, setup: Setup ) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { @@ -74,5 +75,7 @@ export function bucketFetcher( } }; - return client.search(params); + const response = await client.search(params); + + return response; } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index f49cf2b8c8541e..a54fa9c10de13d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -19,10 +19,10 @@ export async function getDistributionMax( transactionType: string, setup: Setup ) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts index cdaddc3af3e956..20152ecf06480c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -19,10 +19,10 @@ export async function getTransaction( traceId: string, setup: Setup ) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 1, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts index 3b48bfc7a98697..ade491be32fc7b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, @@ -14,9 +13,10 @@ import { import { rangeFilter } from '../helpers/range_filter'; import { Setup } from '../helpers/setup_request'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; +import { ESFilter } from '../../../typings/elasticsearch'; export async function getEnvironments(setup: Setup, serviceName?: string) { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const filter: ESFilter[] = [ { terms: { [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'] } }, @@ -31,9 +31,9 @@ export async function getEnvironments(setup: Setup, serviceName?: string) { const params = { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, @@ -58,7 +58,7 @@ export async function getEnvironments(setup: Setup, serviceName?: string) { const environmentsBuckets = idx(aggs, _ => _.environments.buckets) || []; const environments = environmentsBuckets.map( - environmentBucket => environmentBucket.key + environmentBucket => environmentBucket.key as string ); return environments; diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts index 9c9a5c45f697c2..b7aaae2ff26525 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts @@ -53,7 +53,7 @@ export const getFilterAggregations = async ({ terms: { field: field.fieldName, order: { - _count: 'desc' + _count: 'desc' as const } }, ...bucketCountAggregation diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts index 7632b03584167b..e8c29c29434e6e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts @@ -68,7 +68,7 @@ export async function getLocalUIFilters({ options: sortByOrder( aggregationsForFilter.by_terms.buckets.map(bucket => { return { - name: bucket.key, + name: bucket.key as string, count: 'bucket_count' in bucket ? bucket.bucket_count.value diff --git a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts index 682ebf27207c42..7d22d9488718f5 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { indexPatternRoute } from './index_pattern'; +import { indexPatternRoute, kueryBarIndexPatternRoute } from './index_pattern'; import { errorDistributionRoute, errorGroupsRoute, @@ -25,7 +25,12 @@ import { listAgentConfigurationServicesRoute, updateAgentConfigurationRoute, agentConfigurationAgentNameRoute -} from './settings'; +} from './settings/agent_configuration'; +import { + apmIndexSettingsRoute, + apmIndicesRoute, + saveApmIndicesRoute +} from './settings/apm_indices'; import { metricsChartsRoute } from './metrics'; import { serviceNodesRoute } from './service_nodes'; import { tracesRoute, tracesByIdRoute } from './traces'; @@ -53,6 +58,7 @@ const createApmApi = () => { const api = createApi() // index pattern .add(indexPatternRoute) + .add(kueryBarIndexPatternRoute) // Errors .add(errorDistributionRoute) @@ -75,6 +81,11 @@ const createApmApi = () => { .add(listAgentConfigurationServicesRoute) .add(updateAgentConfigurationRoute) + // APM indices + .add(apmIndexSettingsRoute) + .add(apmIndicesRoute) + .add(saveApmIndicesRoute) + // Metrics .add(metricsChartsRoute) .add(serviceNodesRoute) diff --git a/x-pack/legacy/plugins/apm/server/routes/errors.ts b/x-pack/legacy/plugins/apm/server/routes/errors.ts index 73bb6e97b999b4..a315dd10023dcf 100644 --- a/x-pack/legacy/plugins/apm/server/routes/errors.ts +++ b/x-pack/legacy/plugins/apm/server/routes/errors.ts @@ -21,7 +21,7 @@ export const errorsRoute = createRoute(core => ({ query: t.intersection([ t.partial({ sortField: t.string, - sortDirection: t.string + sortDirection: t.union([t.literal('asc'), t.literal('desc')]) }), uiFiltersRt, rangeRt diff --git a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts index e838fb1a4b526a..100df4dc238fe9 100644 --- a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts @@ -3,8 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import * as t from 'io-ts'; import { getAPMIndexPattern } from '../lib/index_pattern'; import { createRoute } from './create_route'; +import { getKueryBarIndexPattern } from '../lib/index_pattern/getKueryBarIndexPattern'; +import { setupRequest } from '../lib/helpers/setup_request'; export const indexPatternRoute = createRoute(core => ({ path: '/api/apm/index_pattern', @@ -13,3 +16,23 @@ export const indexPatternRoute = createRoute(core => ({ return await getAPMIndexPattern(server); } })); + +export const kueryBarIndexPatternRoute = createRoute(core => ({ + path: '/api/apm/kuery_bar_index_pattern', + params: { + query: t.partial({ + processorEvent: t.union([ + t.literal('transaction'), + t.literal('metric'), + t.literal('error') + ]) + }) + }, + handler: async (request, { query }) => { + const { processorEvent } = query; + + const setup = await setupRequest(request); + + return getKueryBarIndexPattern({ request, processorEvent, setup }); + } +})); diff --git a/x-pack/legacy/plugins/apm/server/routes/settings.ts b/x-pack/legacy/plugins/apm/server/routes/settings.ts deleted file mode 100644 index 65ef5df78f3e12..00000000000000 --- a/x-pack/legacy/plugins/apm/server/routes/settings.ts +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as t from 'io-ts'; -import { setupRequest } from '../lib/helpers/setup_request'; -import { getServiceNames } from '../lib/settings/agent_configuration/get_service_names'; -import { createOrUpdateConfiguration } from '../lib/settings/agent_configuration/create_or_update_configuration'; -import { searchConfigurations } from '../lib/settings/agent_configuration/search'; -import { listConfigurations } from '../lib/settings/agent_configuration/list_configurations'; -import { getEnvironments } from '../lib/settings/agent_configuration/get_environments'; -import { deleteConfiguration } from '../lib/settings/agent_configuration/delete_configuration'; -import { createRoute } from './create_route'; -import { transactionSampleRateRt } from '../../common/runtime_types/transaction_sample_rate_rt'; -import { transactionMaxSpansRt } from '../../common/runtime_types/transaction_max_spans_rt'; -import { getAgentNameByService } from '../lib/settings/agent_configuration/get_agent_name_by_service'; -import { markAppliedByAgent } from '../lib/settings/agent_configuration/mark_applied_by_agent'; - -// get list of configurations -export const agentConfigurationRoute = createRoute(core => ({ - path: '/api/apm/settings/agent-configuration', - handler: async req => { - const setup = await setupRequest(req); - return await listConfigurations({ setup }); - } -})); - -// delete configuration -export const deleteAgentConfigurationRoute = createRoute(() => ({ - method: 'DELETE', - path: '/api/apm/settings/agent-configuration/{configurationId}', - params: { - path: t.type({ - configurationId: t.string - }) - }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { configurationId } = path; - return await deleteConfiguration({ - configurationId, - setup - }); - } -})); - -// get list of services -export const listAgentConfigurationServicesRoute = createRoute(() => ({ - method: 'GET', - path: '/api/apm/settings/agent-configuration/services', - handler: async req => { - const setup = await setupRequest(req); - return await getServiceNames({ - setup - }); - } -})); - -const agentPayloadRt = t.intersection([ - t.partial({ agent_name: t.string }), - t.type({ - service: t.intersection([ - t.partial({ name: t.string }), - t.partial({ environment: t.string }) - ]) - }), - t.type({ - settings: t.intersection([ - t.partial({ transaction_sample_rate: transactionSampleRateRt }), - t.partial({ capture_body: t.string }), - t.partial({ transaction_max_spans: transactionMaxSpansRt }) - ]) - }) -]); - -// get environments for service -export const listAgentConfigurationEnvironmentsRoute = createRoute(() => ({ - path: '/api/apm/settings/agent-configuration/environments', - params: { - query: t.partial({ serviceName: t.string }) - }, - handler: async (req, { query }) => { - const setup = await setupRequest(req); - const { serviceName } = query; - return await getEnvironments({ serviceName, setup }); - } -})); - -// get agentName for service -export const agentConfigurationAgentNameRoute = createRoute(() => ({ - path: '/api/apm/settings/agent-configuration/agent_name', - params: { - query: t.type({ serviceName: t.string }) - }, - handler: async (req, { query }) => { - const setup = await setupRequest(req); - const { serviceName } = query; - const agentName = await getAgentNameByService({ serviceName, setup }); - return agentName; - } -})); - -export const createAgentConfigurationRoute = createRoute(() => ({ - method: 'POST', - path: '/api/apm/settings/agent-configuration/new', - params: { - body: agentPayloadRt - }, - handler: async (req, { body }) => { - const setup = await setupRequest(req); - return await createOrUpdateConfiguration({ configuration: body, setup }); - } -})); - -export const updateAgentConfigurationRoute = createRoute(() => ({ - method: 'PUT', - path: '/api/apm/settings/agent-configuration/{configurationId}', - params: { - path: t.type({ - configurationId: t.string - }), - body: agentPayloadRt - }, - handler: async (req, { path, body }) => { - const setup = await setupRequest(req); - const { configurationId } = path; - return await createOrUpdateConfiguration({ - configurationId, - configuration: body, - setup - }); - } -})); - -// Lookup single configuration (used by APM Server) -export const agentConfigurationSearchRoute = createRoute(core => ({ - method: 'POST', - path: '/api/apm/settings/agent-configuration/search', - params: { - body: t.type({ - service: t.intersection([ - t.type({ name: t.string }), - t.partial({ environment: t.string }) - ]), - etag: t.string - }) - }, - handler: async (req, { body }, h) => { - const setup = await setupRequest(req); - const config = await searchConfigurations({ - serviceName: body.service.name, - environment: body.service.environment, - setup - }); - - if (!config) { - return h.response().code(404); - } - - // update `applied_by_agent` field if etags match - if (body.etag === config._source.etag && !config._source.applied_by_agent) { - markAppliedByAgent({ id: config._id, body: config._source, setup }); - } - - return config; - } -})); diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts new file mode 100644 index 00000000000000..d25ad949d6ddef --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { getServiceNames } from '../../lib/settings/agent_configuration/get_service_names'; +import { createOrUpdateConfiguration } from '../../lib/settings/agent_configuration/create_or_update_configuration'; +import { searchConfigurations } from '../../lib/settings/agent_configuration/search'; +import { listConfigurations } from '../../lib/settings/agent_configuration/list_configurations'; +import { getEnvironments } from '../../lib/settings/agent_configuration/get_environments'; +import { deleteConfiguration } from '../../lib/settings/agent_configuration/delete_configuration'; +import { createRoute } from '../create_route'; +import { transactionSampleRateRt } from '../../../common/runtime_types/transaction_sample_rate_rt'; +import { transactionMaxSpansRt } from '../../../common/runtime_types/transaction_max_spans_rt'; +import { getAgentNameByService } from '../../lib/settings/agent_configuration/get_agent_name_by_service'; +import { markAppliedByAgent } from '../../lib/settings/agent_configuration/mark_applied_by_agent'; + +// get list of configurations +export const agentConfigurationRoute = createRoute(core => ({ + path: '/api/apm/settings/agent-configuration', + handler: async req => { + const setup = await setupRequest(req); + return await listConfigurations({ setup }); + } +})); + +// delete configuration +export const deleteAgentConfigurationRoute = createRoute(() => ({ + method: 'DELETE', + path: '/api/apm/settings/agent-configuration/{configurationId}', + params: { + path: t.type({ + configurationId: t.string + }) + }, + handler: async (req, { path }) => { + const setup = await setupRequest(req); + const { configurationId } = path; + return await deleteConfiguration({ + configurationId, + setup + }); + } +})); + +// get list of services +export const listAgentConfigurationServicesRoute = createRoute(() => ({ + method: 'GET', + path: '/api/apm/settings/agent-configuration/services', + handler: async req => { + const setup = await setupRequest(req); + return await getServiceNames({ + setup + }); + } +})); + +const agentPayloadRt = t.intersection([ + t.partial({ agent_name: t.string }), + t.type({ + service: t.intersection([ + t.partial({ name: t.string }), + t.partial({ environment: t.string }) + ]) + }), + t.type({ + settings: t.intersection([ + t.partial({ transaction_sample_rate: transactionSampleRateRt }), + t.partial({ capture_body: t.string }), + t.partial({ transaction_max_spans: transactionMaxSpansRt }) + ]) + }) +]); + +// get environments for service +export const listAgentConfigurationEnvironmentsRoute = createRoute(() => ({ + path: '/api/apm/settings/agent-configuration/environments', + params: { + query: t.partial({ serviceName: t.string }) + }, + handler: async (req, { query }) => { + const setup = await setupRequest(req); + const { serviceName } = query; + return await getEnvironments({ serviceName, setup }); + } +})); + +// get agentName for service +export const agentConfigurationAgentNameRoute = createRoute(() => ({ + path: '/api/apm/settings/agent-configuration/agent_name', + params: { + query: t.type({ serviceName: t.string }) + }, + handler: async (req, { query }) => { + const setup = await setupRequest(req); + const { serviceName } = query; + const agentName = await getAgentNameByService({ serviceName, setup }); + return agentName; + } +})); + +export const createAgentConfigurationRoute = createRoute(() => ({ + method: 'POST', + path: '/api/apm/settings/agent-configuration/new', + params: { + body: agentPayloadRt + }, + handler: async (req, { body }) => { + const setup = await setupRequest(req); + return await createOrUpdateConfiguration({ configuration: body, setup }); + } +})); + +export const updateAgentConfigurationRoute = createRoute(() => ({ + method: 'PUT', + path: '/api/apm/settings/agent-configuration/{configurationId}', + params: { + path: t.type({ + configurationId: t.string + }), + body: agentPayloadRt + }, + handler: async (req, { path, body }) => { + const setup = await setupRequest(req); + const { configurationId } = path; + return await createOrUpdateConfiguration({ + configurationId, + configuration: body, + setup + }); + } +})); + +// Lookup single configuration (used by APM Server) +export const agentConfigurationSearchRoute = createRoute(core => ({ + method: 'POST', + path: '/api/apm/settings/agent-configuration/search', + params: { + body: t.type({ + service: t.intersection([ + t.type({ name: t.string }), + t.partial({ environment: t.string }) + ]), + etag: t.string + }) + }, + handler: async (req, { body }, h) => { + const setup = await setupRequest(req); + const config = await searchConfigurations({ + serviceName: body.service.name, + environment: body.service.environment, + setup + }); + + if (!config) { + return h.response().code(404); + } + + // update `applied_by_agent` field if etags match + if (body.etag === config._source.etag && !config._source.applied_by_agent) { + markAppliedByAgent({ id: config._id, body: config._source, setup }); + } + + return config; + } +})); diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts new file mode 100644 index 00000000000000..3c82a35ec79033 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { createRoute } from '../create_route'; +import { + getApmIndices, + getApmIndexSettings +} from '../../lib/settings/apm_indices/get_apm_indices'; +import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices'; + +// get list of apm indices and values +export const apmIndexSettingsRoute = createRoute(core => ({ + method: 'GET', + path: '/api/apm/settings/apm-index-settings', + handler: async req => { + const { server } = core.http; + const setup = await setupRequest(req); + return await getApmIndexSettings({ setup, server }); + } +})); + +// get apm indices configuration object +export const apmIndicesRoute = createRoute(core => ({ + method: 'GET', + path: '/api/apm/settings/apm-indices', + handler: async req => { + const { server } = core.http; + return await getApmIndices(server); + } +})); + +// save ui indices +export const saveApmIndicesRoute = createRoute(core => ({ + method: 'POST', + path: '/api/apm/settings/apm-indices/save', + params: { + body: t.partial({ + 'apm_oss.sourcemapIndices': t.string, + 'apm_oss.errorIndices': t.string, + 'apm_oss.onboardingIndices': t.string, + 'apm_oss.spanIndices': t.string, + 'apm_oss.transactionIndices': t.string, + 'apm_oss.metricsIndices': t.string, + 'apm_oss.apmAgentConfigurationIndex': t.string + }) + }, + handler: async (req, { body }) => { + const { server } = core.http; + return await saveApmIndices(server, body); + } +})); diff --git a/x-pack/legacy/plugins/apm/typings/common.d.ts b/x-pack/legacy/plugins/apm/typings/common.d.ts new file mode 100644 index 00000000000000..d79b05ed99b496 --- /dev/null +++ b/x-pack/legacy/plugins/apm/typings/common.d.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../infra/types/rison_node'; +import '../../infra/types/eui'; +// EUIBasicTable +import {} from '../../reporting/public/components/report_listing'; +// .svg +import '../../canvas/types/webpack'; + +// Allow unknown properties in an object +export type AllowUnknownProperties = T extends Array + ? Array> + : AllowUnknownObjectProperties; + +type AllowUnknownObjectProperties = T extends object + ? { [Prop in keyof T]: AllowUnknownProperties } & { + [key: string]: unknown; + } + : T; + +export type PromiseReturnType = Func extends ( + ...args: any[] +) => Promise + ? Value + : Func; diff --git a/x-pack/legacy/plugins/apm/typings/common.ts b/x-pack/legacy/plugins/apm/typings/common.ts deleted file mode 100644 index cfe6c9c572dc40..00000000000000 --- a/x-pack/legacy/plugins/apm/typings/common.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export interface StringMap { - [key: string]: T; -} - -// Allow unknown properties in an object -export type AllowUnknownProperties = T extends Array - ? Array> - : AllowUnknownObjectProperties; - -type AllowUnknownObjectProperties = T extends object - ? { [Prop in keyof T]: AllowUnknownProperties } & { - [key: string]: unknown; - } - : T; - -export type PromiseReturnType = Func extends ( - ...args: any[] -) => Promise - ? Value - : Func; - -export type IndexAsString = { - [k: string]: Map[keyof Map]; -} & Map; - -export type Omit = Pick>; diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch.ts deleted file mode 100644 index 08d1e2b37492e5..00000000000000 --- a/x-pack/legacy/plugins/apm/typings/elasticsearch.ts +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { StringMap, IndexAsString } from './common'; - -declare module 'elasticsearch' { - // extending SearchResponse to be able to have typed aggregations - - type ESSearchHit = SearchResponse['hits']['hits'][0]; - - type AggregationType = - | 'date_histogram' - | 'histogram' - | 'terms' - | 'avg' - | 'top_hits' - | 'max' - | 'min' - | 'percentiles' - | 'sum' - | 'extended_stats' - | 'filter' - | 'filters' - | 'cardinality' - | 'sampler' - | 'value_count' - | 'derivative' - | 'bucket_script'; - - type AggOptions = AggregationOptionMap & { - [key: string]: any; - }; - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - export type AggregationOptionMap = { - aggs?: { - [aggregationName: string]: { - [T in AggregationType]?: AggOptions & AggregationOptionMap; - }; - }; - }; - - type SubAggregation = T extends { aggs: any } - ? AggregationResultMap - : {}; - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - type BucketAggregation = { - buckets: Array< - { - key: KeyType; - key_as_string: string; - doc_count: number; - } & (SubAggregation) - >; - }; - - type FilterAggregation = { - doc_count: number; - } & SubAggregation; - - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions - type FiltersAggregation = { - // The filters aggregation can have named filters or anonymous filters, - // which changes the structure of the return - // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filters-aggregation.html - buckets: SubAggregationMap extends { - filters: { filters: Record }; - } - ? { - [key in keyof SubAggregationMap['filters']['filters']]: { - doc_count: number; - } & SubAggregation; - } - : Array< - { - doc_count: number; - } & SubAggregation - >; - }; - - type SamplerAggregation = SubAggregation< - SubAggregationMap - > & { - doc_count: number; - }; - - interface AggregatedValue { - value: number | null; - } - - interface HitsTotal { - value: number; - relation: 'eq' | 'gte'; - } - - type AggregationResultMap = IndexAsString< - { - [AggregationName in keyof AggregationOption]: { - avg: AggregatedValue; - max: AggregatedValue; - min: AggregatedValue; - sum: AggregatedValue; - value_count: AggregatedValue; - // Elasticsearch might return terms with numbers, but this is a more limited type - terms: BucketAggregation; - date_histogram: BucketAggregation< - AggregationOption[AggregationName], - number - >; - histogram: BucketAggregation< - AggregationOption[AggregationName], - number - >; - top_hits: { - hits: { - total: HitsTotal; - max_score: number | null; - hits: Array<{ - _source: AggregationOption[AggregationName] extends { - Mapping: any; - } - ? AggregationOption[AggregationName]['Mapping'] - : never; - }>; - }; - }; - percentiles: { - values: { - [key: string]: number; - }; - }; - extended_stats: { - count: number; - min: number | null; - max: number | null; - avg: number | null; - sum: number; - sum_of_squares: number | null; - variance: number | null; - std_deviation: number | null; - std_deviation_bounds: { - upper: number | null; - lower: number | null; - }; - }; - filter: FilterAggregation; - filters: FiltersAggregation; - cardinality: { - value: number; - }; - sampler: SamplerAggregation; - derivative: BucketAggregation< - AggregationOption[AggregationName], - number - >; - bucket_script: { - value: number | null; - }; - }[AggregationType & keyof AggregationOption[AggregationName]]; - } - >; - - export type AggregationSearchResponseWithTotalHitsAsInt< - HitType, - SearchParams - > = Pick< - SearchResponse, - Exclude, 'aggregations'> - > & - (SearchParams extends { body: Required } - ? { - aggregations?: AggregationResultMap; - } - : {}); - - type Hits = Pick< - SearchResponse['hits'], - Exclude['hits'], 'total'> - > & { - total: HitsTotal; - }; - - export type AggregationSearchResponseWithTotalHitsAsObject< - HitType, - SearchParams - > = Pick< - AggregationSearchResponseWithTotalHitsAsInt, - Exclude< - keyof AggregationSearchResponseWithTotalHitsAsInt, - 'hits' - > - > & { hits: Hits }; - - export interface ESFilter { - [key: string]: { - [key: string]: string | string[] | number | StringMap | ESFilter[]; - }; - } -} diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts new file mode 100644 index 00000000000000..9f17b0197a5b21 --- /dev/null +++ b/x-pack/legacy/plugins/apm/typings/elasticsearch/aggregations.ts @@ -0,0 +1,256 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Unionize } from 'utility-types'; + +type SortOrder = 'asc' | 'desc'; +type SortInstruction = Record; +export type SortOptions = SortOrder | SortInstruction | SortInstruction[]; + +type Script = + | string + | { + lang?: string; + id?: string; + source?: string; + params?: Record; + }; + +type BucketsPath = string | Record; + +type SourceOptions = string | string[]; + +type MetricsAggregationOptions = + | { + field: string; + missing?: number; + } + | { + script?: Script; + }; + +interface MetricsAggregationResponsePart { + value: number | null; +} + +export interface AggregationOptionsByType { + terms: { + field: string; + size?: number; + missing?: string; + order?: SortOptions; + }; + date_histogram: { + field: string; + format?: string; + min_doc_count?: number; + extended_bounds?: { + min: number; + max: number; + }; + } & ({ calendar_interval: string } | { fixed_interval: string }); + histogram: { + field: string; + interval: number; + min_doc_count?: number; + extended_bounds?: { + min?: number | string; + max?: number | string; + }; + }; + avg: MetricsAggregationOptions; + max: MetricsAggregationOptions; + min: MetricsAggregationOptions; + sum: MetricsAggregationOptions; + value_count: MetricsAggregationOptions; + cardinality: MetricsAggregationOptions & { + precision_threshold?: number; + }; + percentiles: { + field: string; + percents?: number[]; + }; + extended_stats: { + field: string; + }; + top_hits: { + from?: number; + size?: number; + sort?: SortOptions; + _source?: SourceOptions; + }; + filter: Record; + filters: { + filters: Record | any[]; + }; + sampler: { + shard_size?: number; + }; + derivative: { + buckets_path: BucketsPath; + }; + bucket_script: { + buckets_path: BucketsPath; + script?: Script; + }; +} + +type AggregationType = keyof AggregationOptionsByType; + +type AggregationOptionsMap = Unionize< + { + [TAggregationType in AggregationType]: AggregationOptionsByType[TAggregationType]; + } +> & { aggs?: AggregationInputMap }; + +export interface AggregationInputMap { + [key: string]: AggregationOptionsMap; +} + +type BucketSubAggregationResponse< + TAggregationInputMap extends AggregationInputMap | undefined, + TDocument +> = TAggregationInputMap extends AggregationInputMap + ? AggregationResponseMap + : {}; + +interface AggregationResponsePart< + TAggregationOptionsMap extends AggregationOptionsMap, + TDocument +> { + terms: { + buckets: Array< + { + doc_count: number; + key: string | number; + } & BucketSubAggregationResponse< + TAggregationOptionsMap['aggs'], + TDocument + > + >; + }; + histogram: { + buckets: Array< + { + doc_count: number; + key: number; + } & BucketSubAggregationResponse< + TAggregationOptionsMap['aggs'], + TDocument + > + >; + }; + date_histogram: { + buckets: Array< + { + doc_count: number; + key: number; + key_as_string: string; + } & BucketSubAggregationResponse< + TAggregationOptionsMap['aggs'], + TDocument + > + >; + }; + avg: MetricsAggregationResponsePart; + sum: MetricsAggregationResponsePart; + max: MetricsAggregationResponsePart; + min: MetricsAggregationResponsePart; + value_count: MetricsAggregationResponsePart; + cardinality: { + value: number; + }; + percentiles: { + values: Record; + }; + extended_stats: { + count: number; + min: number | null; + max: number | null; + avg: number | null; + sum: number | null; + sum_of_squares: number | null; + variance: number | null; + std_deviation: number | null; + std_deviation_bounds: { + upper: number | null; + lower: number | null; + }; + }; + top_hits: { + hits: { + total: { + value: number; + relation: 'eq' | 'gte'; + }; + max_score: number | null; + hits: Array<{ + _source: TDocument; + }>; + }; + }; + filter: { + doc_count: number; + } & AggregationResponseMap; + filters: TAggregationOptionsMap extends { filters: { filters: any[] } } + ? Array< + { doc_count: number } & AggregationResponseMap< + TAggregationOptionsMap['aggs'], + TDocument + > + > + : (TAggregationOptionsMap extends { + filters: { + filters: Record; + }; + } + ? { + buckets: { + [key in keyof TAggregationOptionsMap['filters']['filters']]: { + doc_count: number; + } & AggregationResponseMap< + TAggregationOptionsMap['aggs'], + TDocument + >; + }; + } + : never); + sampler: { + doc_count: number; + } & AggregationResponseMap; + derivative: + | { + value: number; + } + | undefined; + bucket_script: + | { + value: number | null; + } + | undefined; +} + +// Type for debugging purposes. If you see an error in AggregationResponseMap +// similar to "cannot be used to index type", uncomment the type below and hover +// over it to see what aggregation response types are missing compared to the +// input map. + +// type MissingAggregationResponseTypes = Exclude< +// AggregationType, +// keyof AggregationResponsePart<{}> +// >; + +export type AggregationResponseMap< + TAggregationInputMap extends AggregationInputMap | undefined, + TDocument +> = TAggregationInputMap extends AggregationInputMap + ? { + [TName in keyof TAggregationInputMap]: AggregationResponsePart< + TAggregationInputMap[TName], + TDocument + >[AggregationType & keyof TAggregationInputMap[TName]]; + } + : undefined; diff --git a/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts b/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts new file mode 100644 index 00000000000000..56cd0ff23a3fb5 --- /dev/null +++ b/x-pack/legacy/plugins/apm/typings/elasticsearch/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchParams, SearchResponse } from 'elasticsearch'; +import { AggregationResponseMap, AggregationInputMap } from './aggregations'; + +export interface ESSearchBody { + query?: any; + size?: number; + aggs?: AggregationInputMap; + track_total_hits?: boolean | number; +} + +export type ESSearchRequest = Omit & { + body?: ESSearchBody; +}; + +export interface ESSearchOptions { + restTotalHitsAsInt: boolean; +} + +export type ESSearchHit = SearchResponse['hits']['hits'][0]; + +export type ESSearchResponse< + TDocument, + TSearchRequest extends ESSearchRequest, + TOptions extends ESSearchOptions = { restTotalHitsAsInt: false } +> = Omit, 'aggregations' | 'hits'> & + (TSearchRequest extends { body: { aggs: AggregationInputMap } } + ? { + aggregations?: AggregationResponseMap< + TSearchRequest['body']['aggs'], + TDocument + >; + } + : {}) & + ({ + hits: Omit['hits'], 'total'> & + (TOptions['restTotalHitsAsInt'] extends true + ? { + total: number; + } + : { + total: { + value: number; + relation: 'eq' | 'gte'; + }; + }); + }); + +export interface ESFilter { + [key: string]: { + [key: string]: + | string + | string[] + | number + | Record + | ESFilter[]; + }; +} diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts index 545bcc86b2f951..2f6e857e82470a 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts @@ -21,7 +21,7 @@ interface Processor { event: 'error'; } -interface Exception { +export interface Exception { message?: string; // either message or type are given type?: string; module?: string; diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx index 64e7bf6b7f09e5..2ae475475829ff 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx @@ -18,29 +18,23 @@ interface SuggestionItemProps { suggestion: AutocompleteSuggestion; } -export class SuggestionItem extends React.Component { - public static defaultProps: Partial = { - isSelected: false, - }; +export const SuggestionItem: React.SFC = props => { + const { isSelected, onClick, onMouseEnter, suggestion } = props; - public render() { - const { isSelected, onClick, onMouseEnter, suggestion } = this.props; + return ( + + + + + {suggestion.text} + {suggestion.description} + + ); +}; - return ( - - - - - {suggestion.text} - {suggestion.description} - - ); - } -} +SuggestionItem.defaultProps = { + isSelected: false, +}; const SuggestionItemContainer = styled.div<{ isSelected?: boolean; diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index d4a42ba662db41..526728bd77cac6 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -7,16 +7,16 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; -import { RestAPIAdapter } from '../rest_api/adapter_types'; import { ElasticsearchAdapter } from './adapter_types'; import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { setup as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; const getAutocompleteProvider = (language: string) => npStart.plugins.data.autocomplete.getProvider(language); export class RestElasticsearchAdapter implements ElasticsearchAdapter { private cachedIndexPattern: any = null; - constructor(private readonly api: RestAPIAdapter, private readonly indexPatternName: string) {} + constructor(private readonly indexPatternName: string) {} public isKueryValid(kuery: string): boolean { try { @@ -65,9 +65,9 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { if (this.cachedIndexPattern) { return this.cachedIndexPattern; } - const res = await this.api.get( - `/api/index_patterns/_fields_for_wildcard?pattern=${this.indexPatternName}` - ); + const res = await data.indexPatterns.indexPatterns.getFieldsForWildcard({ + pattern: this.indexPatternName, + }); if (isEmpty(res.fields)) { return; } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/legacy/plugins/beats_management/public/lib/compose/kibana.ts index 3d8ca51e006bf7..2ebda89ba13fd6 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/compose/kibana.ts @@ -35,7 +35,7 @@ const onKibanaReady = chrome.dangerouslyGetActiveInjector; export function compose(): FrontendLibs { const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); - const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.BEATS); + const esAdapter = new RestElasticsearchAdapter(INDEX_NAMES.BEATS); const elasticsearchLib = new ElasticsearchLib(esAdapter); const configBlocks = new ConfigBlocksLib( new RestConfigBlocksAdapter(api), diff --git a/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx b/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx index 6ac66437557656..26f2aa80de7639 100644 --- a/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx +++ b/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx @@ -6,93 +6,90 @@ import { EuiBetaBadge, EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import React, { Component } from 'react'; +import React from 'react'; import { NoDataLayout } from '../../../components/layouts/no_data'; import { WalkthroughLayout } from '../../../components/layouts/walkthrough'; import { ChildRoutes } from '../../../components/navigation/child_routes'; import { ConnectedLink } from '../../../components/navigation/connected_link'; import { AppPageProps } from '../../../frontend_types'; -class InitialWalkthroughPageComponent extends Component< - AppPageProps & { - intl: InjectedIntl; - } -> { - public render() { - const { intl } = this.props; +type Props = AppPageProps & { + intl: InjectedIntl; +}; - if (this.props.location.pathname === '/walkthrough/initial') { - return ( - - {'Beats central management '} - - - - - } - actionSection={ - - - {' '} - - - } - > -

- -

-
- ); - } +const InitialWalkthroughPageComponent: React.SFC = props => { + if (props.location.pathname === '/walkthrough/initial') { return ( - { - // FIXME implament goto - }} - activePath={this.props.location.pathname} + + {'Beats central management '} + + + + + } + actionSection={ + + + {' '} + + + } > - - +

+ +

+ ); } -} + return ( + { + // FIXME implament goto + }} + activePath={props.location.pathname} + > + + + ); +}; + export const InitialWalkthroughPage = injectI18n(InitialWalkthroughPageComponent); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts index cad46e81ffc0cd..063e69d1d21416 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionType } from 'src/plugins/expressions/common'; +import { ExpressionType } from 'src/plugins/expressions/public'; import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { EmbeddableTypes } from './embeddable_types'; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index c94b0dcd2bd9d9..546e8967a74397 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -6,8 +6,8 @@ // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants'; -import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/constants'; +import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; export const EmbeddableTypes = { map: MAP_SAVED_OBJECT_TYPE, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts index 958d9c6a3a6f08..abaa16c4e32710 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Filter as ESFilterType } from '@kbn/es-query'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/public'; import { TimeRange } from 'src/plugins/data/public'; import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; @@ -15,6 +14,7 @@ import { EmbeddableExpression, } from '../../expression_types'; import { getFunctionHelp } from '../../../i18n'; +import { esFilters } from '../../../../../../../src/plugins/data/public'; interface Arguments { id: string; @@ -29,7 +29,7 @@ interface SavedMapInput extends EmbeddableInput { isPaused: boolean; interval: number; }; - filters: ESFilterType[]; + filters: esFilters.Filter[]; } type Return = EmbeddableExpression; diff --git a/x-pack/legacy/plugins/canvas/common/lib/constants.ts b/x-pack/legacy/plugins/canvas/common/lib/constants.ts index d4b092c830f72c..7494ea13e6c08e 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/constants.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/constants.ts @@ -36,6 +36,6 @@ export const DATATABLE_COLUMN_TYPES = ['string', 'number', 'null', 'boolean', 'd export const LAUNCHED_FULLSCREEN = 'workpad-full-screen-launch'; export const LAUNCHED_FULLSCREEN_AUTOPLAY = 'workpad-full-screen-launch-with-autoplay'; export const API_ROUTE_SHAREABLE_BASE = '/public/canvas'; -export const API_ROUTE_SHAREABLE_ZIP = `${API_ROUTE_SHAREABLE_BASE}/zip`; -export const API_ROUTE_SHAREABLE_RUNTIME = `${API_ROUTE_SHAREABLE_BASE}/runtime`; -export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `${API_ROUTE_SHAREABLE_BASE}/${SHAREABLE_RUNTIME_NAME}.js`; +export const API_ROUTE_SHAREABLE_ZIP = '/public/canvas/zip'; +export const API_ROUTE_SHAREABLE_RUNTIME = '/public/canvas/runtime'; +export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `/public/canvas/${SHAREABLE_RUNTIME_NAME}.js`; diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot index 5bb55dd1a8b60c..89ed05a1eea93c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot @@ -19,16 +19,17 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` >
Asset thumbnail
@@ -213,16 +214,17 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` >
Asset thumbnail
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx index f933d9009d3674..a65ec1ddba0810 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx @@ -58,7 +58,7 @@ export const SnippetsStep: FC<{ onCopy: OnCopyFn }> = ({ onCopy }) => ( - kbn-canvas-shareable="canvas" ({strings.getRequiredLabel()}) + kbn-canvas-shareable="canvas" ({strings.getRequiredLabel()}) {strings.getShareableParameterDescription()} diff --git a/x-pack/legacy/plugins/canvas/public/lib/clipboard.js b/x-pack/legacy/plugins/canvas/public/lib/clipboard.js index 2cebf2a5bad968..f9d68769c9c3a0 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/clipboard.js +++ b/x-pack/legacy/plugins/canvas/public/lib/clipboard.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Storage } from 'ui/storage'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { LOCALSTORAGE_CLIPBOARD } from '../../common/lib/constants'; import { getWindow } from './get_window'; diff --git a/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js b/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js index 11723587b057dc..f867e4dc77a119 100644 --- a/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js +++ b/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js @@ -16,10 +16,10 @@ const execa = require('execa'); const asyncPipeline = promisify(pipeline); const { - SHAREABLE_RUNTIME_SRC: RUNTIME_SRC, + SHAREABLE_RUNTIME_SRC, KIBANA_ROOT, STATS_OUTPUT, - SHAREABLE_RUNTIME_FILE: RUNTIME_FILE, + SHAREABLE_RUNTIME_FILE, } = require('../shareable_runtime/constants'); run( @@ -36,11 +36,11 @@ run( ...options, }); - const webpackConfig = path.resolve(RUNTIME_SRC, 'webpack.config.js'); + const webpackConfig = path.resolve(SHAREABLE_RUNTIME_SRC, 'webpack.config.js'); const clean = () => { log.info('Deleting previous build.'); - del.sync([RUNTIME_FILE], { force: true }); + del.sync([SHAREABLE_RUNTIME_FILE], { force: true }); }; if (flags.clean) { @@ -66,7 +66,7 @@ run( '--display-entrypoints', 'false', '--content-base', - RUNTIME_SRC, + SHAREABLE_RUNTIME_SRC, ], options ); @@ -91,10 +91,20 @@ run( clean(); log.info('Building Canvas Shareable Workpad Runtime...'); - execa.sync('yarn', ['webpack', '--config', webpackConfig, '--hide-modules', '--progress'], { - ...options, - env, - }); + execa.sync( + 'yarn', + [ + 'webpack', + '--config', + webpackConfig, + '--hide-modules', + ...(process.stdout.isTTY ? ['--progress'] : []), + ], + { + ...options, + env, + } + ); log.success('...runtime built!'); }, { diff --git a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts index 8de813255a2301..ca34246531bff5 100644 --- a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts +++ b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { buildQueryFilter, Filter as ESFilterType } from '@kbn/es-query'; import { TimeRange } from 'src/plugins/data/public'; import { Filter } from '../../types'; // @ts-ignore Untyped Local import { buildBoolArray } from './build_bool_array'; +import { esFilters } from '../../../../../../src/plugins/data/common'; export interface EmbeddableFilterInput { - filters: ESFilterType[]; + filters: esFilters.Filter[]; timeRange?: TimeRange; } @@ -30,8 +30,10 @@ function getTimeRangeFromFilters(filters: Filter[]): TimeRange | undefined { : undefined; } -function getQueryFilters(filters: Filter[]): ESFilterType[] { - return buildBoolArray(filters.filter(filter => filter.type !== 'time')).map(buildQueryFilter); +function getQueryFilters(filters: Filter[]): esFilters.Filter[] { + return buildBoolArray(filters.filter(filter => filter.type !== 'time')).map( + esFilters.buildQueryFilter + ); } export function buildEmbeddableFilters(filters: Filter[]): EmbeddableFilterInput { diff --git a/x-pack/legacy/plugins/canvas/server/routes/shareables.ts b/x-pack/legacy/plugins/canvas/server/routes/shareables.ts index cdb6a810519963..e8186ceceb47f2 100644 --- a/x-pack/legacy/plugins/canvas/server/routes/shareables.ts +++ b/x-pack/legacy/plugins/canvas/server/routes/shareables.ts @@ -25,8 +25,14 @@ export function shareableWorkpads(route: CoreSetup['http']['route']) { route({ method: 'GET', path: API_ROUTE_SHAREABLE_RUNTIME, + handler: { - file: SHAREABLE_RUNTIME_FILE, + file: { + path: SHAREABLE_RUNTIME_FILE, + // The option setting is not for typical use. We're using it here to avoid + // problems in Cloud environments. See elastic/kibana#47405. + confine: false, + }, }, }); @@ -34,9 +40,12 @@ export function shareableWorkpads(route: CoreSetup['http']['route']) { route({ method: 'GET', path: API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD, + handler(_request, handler) { + // The option setting is not for typical use. We're using it here to avoid + // problems in Cloud environments. See elastic/kibana#47405. // @ts-ignore No type for inert Hapi handler - const file = handler.file(SHAREABLE_RUNTIME_FILE); + const file = handler.file(SHAREABLE_RUNTIME_FILE, { confine: false }); file.type('application/octet-stream'); return file; }, diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx index ea944bd30e9b83..a55c87c2d74a2c 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx @@ -32,7 +32,7 @@ describe('Canvas Shareable Workpad API', () => { test('Placed successfully with default properties', async () => { const container = document.createElement('div'); document.body.appendChild(container); - const wrapper = mount(
, { + const wrapper = mount(
, { attachTo: container, }); @@ -46,11 +46,7 @@ describe('Canvas Shareable Workpad API', () => { const container = document.createElement('div'); document.body.appendChild(container); const wrapper = mount( -
, +
, { attachTo: container, } @@ -69,11 +65,7 @@ describe('Canvas Shareable Workpad API', () => { const container = document.createElement('div'); document.body.appendChild(container); const wrapper = mount( -
, +
, { attachTo: container, } @@ -97,7 +89,7 @@ describe('Canvas Shareable Workpad API', () => { kbn-canvas-width="350" kbn-canvas-height="350" kbn-canvas-url="workpad.json" - >
, + />, { attachTo: container, } @@ -116,7 +108,7 @@ describe('Canvas Shareable Workpad API', () => { const container = document.createElement('div'); document.body.appendChild(container); const wrapper = mount( -
, +
, { attachTo: container, } diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap index baa0b509ccb73a..072cf01255a0d2 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap @@ -17,8 +17,11 @@ exports[` can navigate Autoplay Settings 1`] = ` data-focus-lock-disabled="disabled" >