diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy new file mode 100644 index 00000000000000..5cf6efe324ac3f --- /dev/null +++ b/.ci/end2end.groovy @@ -0,0 +1,125 @@ +#!/usr/bin/env groovy + +library identifier: 'apm@current', +retriever: modernSCM( + [$class: 'GitSCMSource', + credentialsId: 'f94e9298-83ae-417e-ba91-85c279771570', + id: '37cf2c00-2cc7-482e-8c62-7bbffef475e2', + remote: 'git@github.com:elastic/apm-pipeline-library.git']) + +pipeline { + agent { label 'linux && immutable' } + environment { + BASE_DIR = 'src/github.com/elastic/kibana' + HOME = "${env.WORKSPACE}" + APM_ITS = 'apm-integration-testing' + CYPRESS_DIR = 'x-pack/legacy/plugins/apm/cypress' + PIPELINE_LOG_LEVEL = 'DEBUG' + } + options { + timeout(time: 1, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr: '40', artifactNumToKeepStr: '20', daysToKeepStr: '30')) + timestamps() + ansiColor('xterm') + disableResume() + durabilityHint('PERFORMANCE_OPTIMIZED') + } + triggers { + issueCommentTrigger('(?i).*jenkins\\W+run\\W+(?:the\\W+)?e2e(?:\\W+please)?.*') + } + parameters { + booleanParam(name: 'FORCE', defaultValue: false, description: 'Whether to force the run.') + } + stages { + stage('Checkout') { + options { skipDefaultCheckout() } + steps { + deleteDir() + gitCheckout(basedir: "${BASE_DIR}", githubNotifyFirstTimeContributor: false, + shallow: false, reference: "/var/lib/jenkins/.git-references/kibana.git") + script { + dir("${BASE_DIR}"){ + def regexps =[ "^x-pack/legacy/plugins/apm/.*" ] + env.APM_UPDATED = isGitRegionMatch(patterns: regexps) + } + } + dir("${APM_ITS}"){ + git changelog: false, + credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba', + poll: false, + url: "git@github.com:elastic/${APM_ITS}.git" + } + } + } + stage('Start services') { + options { skipDefaultCheckout() } + when { + anyOf { + expression { return params.FORCE } + expression { return env.APM_UPDATED != "false" } + } + } + steps { + dir("${APM_ITS}"){ + sh './scripts/compose.py start master --no-kibana --no-xpack-secure' + } + } + } + stage('Prepare Kibana') { + options { skipDefaultCheckout() } + when { + anyOf { + expression { return params.FORCE } + expression { return env.APM_UPDATED != "false" } + } + } + environment { + JENKINS_NODE_COOKIE = 'dontKillMe' + } + steps { + dir("${BASE_DIR}"){ + sh script: "${CYPRESS_DIR}/ci/prepare-kibana.sh" + } + } + } + stage('Smoke Tests'){ + options { skipDefaultCheckout() } + when { + anyOf { + expression { return params.FORCE } + expression { return env.APM_UPDATED != "false" } + } + } + steps{ + dir("${BASE_DIR}"){ + sh ''' + jobs -l + docker build --tag cypress ${CYPRESS_DIR}/ci + docker run --rm -t --user "$(id -u):$(id -g)" \ + -v `pwd`:/app --network="host" \ + --name cypress cypress''' + } + } + post { + always { + dir("${BASE_DIR}"){ + archiveArtifacts(allowEmptyArchive: false, artifacts: "${CYPRESS_DIR}/screenshots/**,${CYPRESS_DIR}/videos/**,${CYPRESS_DIR}/*e2e-tests.xml") + junit(allowEmptyResults: true, testResults: "${CYPRESS_DIR}/*e2e-tests.xml") + } + dir("${APM_ITS}"){ + sh 'docker-compose logs > apm-its.log || true' + sh 'docker-compose down -v || true' + archiveArtifacts(allowEmptyArchive: false, artifacts: 'apm-its.log') + } + } + } + } + } + post { + always { + dir("${BASE_DIR}"){ + archiveArtifacts(allowEmptyArchive: true, artifacts: "${CYPRESS_DIR}/ingest-data.log,kibana.log") + } + } + } +} diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index b697f22c009d1c..ab68a60dcfc279 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -44,6 +44,7 @@ tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ x-pack/legacy/plugins/*/node_modules \ x-pack/legacy/plugins/reporting/.chromium \ test/plugin_functional/plugins/*/node_modules \ + examples/*/node_modules \ .es \ .chromedriver \ .geckodriver; diff --git a/.eslintrc.js b/.eslintrc.js index fe546ec02a6688..367ac892107abf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -163,12 +163,6 @@ module.exports = { '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: { @@ -176,13 +170,6 @@ module.exports = { '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: { @@ -215,13 +202,6 @@ module.exports = { '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 @@ -337,6 +317,7 @@ module.exports = { '!src/core/server/index.ts', '!src/core/server/mocks.ts', '!src/core/server/types.ts', + '!src/core/server/test_utils.ts', // for absolute imports until fixed in // https://github.com/elastic/kibana/issues/36096 '!src/core/server/types', @@ -350,6 +331,21 @@ module.exports = { ], allowSameFolder: true, }, + { + target: ['src/core/**/*'], + from: ['x-pack/**/*'], + errorMessage: 'OSS cannot import x-pack files.', + }, + { + target: ['src/core/**/*'], + from: [ + 'plugins/**/*', + 'src/plugins/**/*', + 'src/legacy/core_plugins/**/*', + 'src/legacy/ui/**/*', + ], + errorMessage: 'The core cannot depend on any plugins.', + }, { from: ['src/legacy/ui/**/*', 'ui/**/*'], target: [ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 111619d8330378..611f0dba6e1a52 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,6 +5,9 @@ # App /x-pack/legacy/plugins/lens/ @elastic/kibana-app /x-pack/legacy/plugins/graph/ @elastic/kibana-app +/src/plugins/share/ @elastic/kibana-app +/src/legacy/server/url_shortening/ @elastic/kibana-app +/src/legacy/server/sample_data/ @elastic/kibana-app # App Architecture /src/plugins/data/ @elastic/kibana-app-arch @@ -16,6 +19,16 @@ /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 +/src/legacy/core_plugins/data/ @elastic/kibana-app-arch +/src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch +/src/legacy/core_plugins/interpreter/ @elastic/kibana-app-arch +/src/legacy/core_plugins/kibana_react/ @elastic/kibana-app-arch +/src/legacy/core_plugins/kibana/public/management/ @elastic/kibana-app-arch +/src/legacy/core_plugins/kibana/server/field_formats/ @elastic/kibana-app-arch +/src/legacy/core_plugins/kibana/server/routes/api/management/ @elastic/kibana-app-arch +/src/legacy/core_plugins/kibana/server/routes/api/suggestions/ @elastic/kibana-app-arch +/src/legacy/core_plugins/visualizations/ @elastic/kibana-app-arch +/src/legacy/server/index_patterns/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui @@ -54,14 +67,25 @@ /packages/kbn-es/ @elastic/kibana-operations /packages/kbn-pm/ @elastic/kibana-operations /packages/kbn-test/ @elastic/kibana-operations +/src/legacy/server/keystore/ @elastic/kibana-operations +/src/legacy/server/pid/ @elastic/kibana-operations +/src/legacy/server/sass/ @elastic/kibana-operations +/src/legacy/server/utils/ @elastic/kibana-operations +/src/legacy/server/warnings/ @elastic/kibana-operations # Platform /src/core/ @elastic/kibana-platform -/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 /packages/kbn-config-schema/ @elastic/kibana-platform +/src/legacy/server/config/ @elastic/kibana-platform +/src/legacy/server/csp/ @elastic/kibana-platform +/src/legacy/server/http/ @elastic/kibana-platform +/src/legacy/server/i18n/ @elastic/kibana-platform +/src/legacy/server/logging/ @elastic/kibana-platform +/src/legacy/server/saved_objects/ @elastic/kibana-platform +/src/legacy/server/status/ @elastic/kibana-platform # Security /x-pack/legacy/plugins/security/ @elastic/kibana-security diff --git a/.gitignore b/.gitignore index 02b20da297fc67..e7391a5c292d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ disabledPlugins webpackstats.json /config/* !/config/kibana.yml +!/config/apm.js coverage selenium .babel_register_cache.json diff --git a/.i18nrc.json b/.i18nrc.json index e5ba6762da154d..a1c49ae03f3593 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -17,8 +17,10 @@ "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", + "management": "src/legacy/core_plugins/management", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", + "kibana_utils": "src/plugins/kibana_utils", "navigation": "src/legacy/core_plugins/navigation", "newsfeed": "src/plugins/newsfeed", "regionMap": "src/legacy/core_plugins/region_map", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e2a8459c2b01ab..599cf26970030e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,7 @@ A high level overview of our contributing guidelines. - [Internationalization](#internationalization) - [Testing and Building](#testing-and-building) - [Debugging server code](#debugging-server-code) + - [Instrumenting with Elastic APM](#instrumenting-with-elastic-apm) - [Debugging Unit Tests](#debugging-unit-tests) - [Unit Testing Plugins](#unit-testing-plugins) - [Cross-browser compatibility](#cross-browser-compatibility) @@ -374,6 +375,29 @@ macOS users on a machine with a discrete graphics card may see significant speed ### Debugging Server Code `yarn debug` will start the server with Node's inspect flag. Kibana's development mode will start three processes on ports `9229`, `9230`, and `9231`. Chrome's developer tools need to be configured to connect to all three connections. Add `localhost:` for each Kibana process in Chrome's developer tools connection tab. +### Instrumenting with Elastic APM +Kibana ships with the [Elastic APM Node.js Agent](https://github.com/elastic/apm-agent-nodejs) built-in for debugging purposes. + +Its default configuration is meant to be used by core Kibana developers only, but it can easily be re-configured to your needs. +In its default configuration it's disabled and will, once enabled, send APM data to a centrally managed Elasticsearch cluster accessible only to Elastic employees. + +To change the location where data is sent, use the [`serverUrl`](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#server-url) APM config option. +To activate the APM agent, use the [`active`](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#active) APM config option. + +All config options can be set either via environment variables, or by creating an appropriate config file under `config/apm.dev.js`. +For more information about configuring the APM agent, please refer to [the documentation](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuring-the-agent.html). + +Example `config/apm.dev.js` file: + +```js +module.exports = { + active: true, +}; +``` + +Once the agent is active, it will trace all incoming HTTP requests to Kibana, monitor for errors, and collect process-level metrics. +The collected data will be sent to the APM Server and is viewable in the APM UI in Kibana. + ### Unit testing frameworks Kibana is migrating unit testing from Mocha to Jest. Legacy unit tests still exist in Mocha but all new unit tests should be written in Jest. Mocha tests @@ -389,7 +413,7 @@ The following table outlines possible test file locations and how to invoke them | Jest | `src/**/*.test.js`
`src/**/*.test.ts` | `node scripts/jest -t regexp [test path]` | | Jest (integration) | `**/integration_tests/**/*.test.js` | `node scripts/jest_integration -t regexp [test path]` | | Mocha | `src/**/__tests__/**/*.js`
`!src/**/public/__tests__/*.js`
`packages/kbn-datemath/test/**/*.js`
`packages/kbn-dev-utils/src/**/__tests__/**/*.js`
`tasks/**/__tests__/**/*.js` | `node scripts/mocha --grep=regexp [test path]` | -| Functional | `test/*integration/**/config.js`
`test/*functional/**/config.js` | `node scripts/functional_tests_server --config test/[directory]/config.js`
`node scripts/functional_test_runner --config test/[directory]/config.js --grep=regexp` | +| Functional | `test/*integration/**/config.js`
`test/*functional/**/config.js`
`test/accessibility/config.js` | `node scripts/functional_tests_server --config test/[directory]/config.js`
`node scripts/functional_test_runner --config test/[directory]/config.js --grep=regexp` | | Karma | `src/**/public/__tests__/*.js` | `npm run test:dev` | For X-Pack tests located in `x-pack/` see [X-Pack Testing](x-pack/README.md#testing) diff --git a/Jenkinsfile b/Jenkinsfile index 6030f2b4a021d8..f6844c7f03fe62 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,43 +7,45 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a timeout(time: 120, unit: 'MINUTES') { timestamps { ansiColor('xterm') { - catchError { - parallel([ - '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', 'Execute kibana-firefoxSmoke') }), - 'oss-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld('./test/scripts/jenkins_accessibility.sh', 'Execute kibana-accessibility') }), - 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') }), - ]), - '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', 'Execute xpack-firefoxSmoke') }), - 'xpack-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld('./test/scripts/jenkins_xpack_accessibility.sh', 'Execute xpack-accessibility') }), - 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld('./test/scripts/jenkins_xpack_visual_regression.sh', 'Execute xpack-visualRegression') }), - ]), - ]) + githubPr.withDefaultPrComments { + catchError { + parallel([ + '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-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld('./test/scripts/jenkins_firefox_smoke.sh', 'Execute kibana-firefoxSmoke') }), + '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-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld('./test/scripts/jenkins_accessibility.sh', 'Execute kibana-accessibility') }), + // 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') }), + ]), + 'kibana-xpack-agent': kibanaPipeline.withWorkers('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ + 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld('./test/scripts/jenkins_xpack_firefox_smoke.sh', 'Execute xpack-firefoxSmoke') }), + '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-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld('./test/scripts/jenkins_xpack_accessibility.sh', 'Execute xpack-accessibility') }), + // 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld('./test/scripts/jenkins_xpack_visual_regression.sh', 'Execute xpack-visualRegression') }), + ]), + ]) + } } kibanaPipeline.sendMail() } diff --git a/NOTICE.txt b/NOTICE.txt index 989bb6f754a091..230e5117460224 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -186,32 +186,6 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -This product includes code that is based on facebookincubator/idx, which was -available under a "MIT" license. - -MIT License - -Copyright (c) 2013-present, Facebook, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - --- This product includes code that was extracted from angular@1.3. Original license: diff --git a/config/apm.js b/config/apm.js new file mode 100644 index 00000000000000..8efbbf87487e36 --- /dev/null +++ b/config/apm.js @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * DO NOT EDIT THIS FILE! + * + * This file contains the configuration for the Elastic APM instrumentaion of + * Kibana itself and is only intented to be used during development of Kibana. + * + * Instrumentation is turned off by default. Once activated it will send APM + * data to an Elasticsearch cluster accessible by Elastic employees. + * + * To modify the configuration, either use environment variables, or create a + * file named `config/apm.dev.js`, which exports a config object as described + * in the docs. + * + * For an overview over the available configuration files, see: + * https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html + * + * For general information about Elastic APM, see: + * https://www.elastic.co/guide/en/apm/get-started/current/index.html + */ + +const { readFileSync } = require('fs'); +const { join } = require('path'); +const { execSync } = require('child_process'); +const merge = require('lodash.merge'); + +module.exports = merge({ + active: false, + serverUrl: 'https://f1542b814f674090afd914960583265f.apm.us-central1.gcp.cloud.es.io:443', + // The secretToken below is intended to be hardcoded in this file even though + // it makes it public. This is not a security/privacy issue. Normally we'd + // instead disable the need for a secretToken in the APM Server config where + // the data is transmitted to, but due to how it's being hosted, it's easier, + // for now, to simply leave it in. + secretToken: 'R0Gjg46pE9K9wGestd', + globalLabels: {}, + centralConfig: false, + logUncaughtExceptions: true +}, devConfig()); + +const rev = gitRev(); +if (rev !== null) module.exports.globalLabels.git_rev = rev; + +try { + const filename = join(__dirname, '..', 'data', 'uuid'); + module.exports.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8'); +} catch (e) {} // eslint-disable-line no-empty + +function gitRev() { + try { + return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim(); + } catch (e) { + return null; + } +} + +function devConfig() { + try { + return require('./apm.dev'); // eslint-disable-line import/no-unresolved + } catch (e) { + return {}; + } +} diff --git a/config/kibana.yml b/config/kibana.yml index 47482f9e6d59f8..0780841ca057ed 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -51,7 +51,7 @@ # Optional settings that provide the paths to the PEM-format SSL certificate and 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. +# xpack.security.http.ssl.client_authentication in Elasticsearch is set to required. #elasticsearch.ssl.certificate: /path/to/your/client.crt #elasticsearch.ssl.key: /path/to/your/client.key diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc index 6d2c5a72f0532b..77a2bfe77b4ab5 100644 --- a/docs/developer/core/development-functional-tests.asciidoc +++ b/docs/developer/core/development-functional-tests.asciidoc @@ -98,6 +98,7 @@ When run without any arguments the `FunctionalTestRunner` automatically loads th * `--config test/functional/config.js` starts Elasticsearch and Kibana servers with the WebDriver tests configured to run in Chrome. * `--config test/functional/config.firefox.js` starts Elasticsearch and Kibana servers with the WebDriver tests configured to run in Firefox. * `--config test/api_integration/config.js` starts Elasticsearch and Kibana servers with the api integration tests configuration. +* `--config test/accessibility/config.ts` starts Elasticsearch and Kibana servers with the WebDriver tests configured to run an accessibility audit using https://www.deque.com/axe/[axe]. There are also command line flags for `--bail` and `--grep`, which behave just like their mocha counterparts. For instance, use `--grep=foo` to run only tests that match a regular expression. @@ -281,7 +282,7 @@ The `FunctionalTestRunner` comes with three built-in services: * Source: {blob}src/functional_test_runner/lib/lifecycle.ts[src/functional_test_runner/lib/lifecycle.ts] * Designed primary for use in services * Exposes lifecycle events for basic coordination. Handlers can return a promise and resolve/fail asynchronously -* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup`, `phaseStart`, `phaseEnd` +* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup` [float] ===== Kibana Services @@ -362,7 +363,7 @@ Full list of services that are used in functional tests can be found here: {blob ** Source: {blob}test/functional/services/remote/remote.ts[test/functional/services/remote/remote.ts] ** Instance of https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html[WebDriver] class ** Responsible for all communication with the browser -** To perform browser actions, use `remote` service +** To perform browser actions, use `remote` service ** For searching and manipulating with DOM elements, use `testSubjects` and `find` services ** See the https://seleniumhq.github.io/selenium/docs/api/javascript/[selenium-webdriver docs] for the full API. diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md index f4dee0f29af342..e01a5e9da50d54 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md @@ -17,7 +17,8 @@ core: { i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; - uiSettings: UiSettingsClientContract; + savedObjects: SavedObjectsStart; + uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; }; diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.md index 97d143d518f60b..68a1c27b118366 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.md @@ -16,5 +16,5 @@ export interface AppMountContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
uiSettings: UiSettingsClientContract;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | +| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
savedObjects: SavedObjectsStart;
uiSettings: IUiSettingsClient;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md index 153e06d591404d..d5d99f3d5be65f 100644 --- a/docs/development/core/public/kibana-plugin-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-public.chromestart.md @@ -39,6 +39,7 @@ export interface ChromeStart | [setBrand(brand)](./kibana-plugin-public.chromestart.setbrand.md) | Set the brand configuration. | | [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs | | [setHelpExtension(helpExtension)](./kibana-plugin-public.chromestart.sethelpextension.md) | Override the current set of custom help content | +| [setHelpSupportUrl(url)](./kibana-plugin-public.chromestart.sethelpsupporturl.md) | Override the default support URL shown in the help menu | | [setIsCollapsed(isCollapsed)](./kibana-plugin-public.chromestart.setiscollapsed.md) | Set the collapsed state of the chrome navigation. | | [setIsVisible(isVisible)](./kibana-plugin-public.chromestart.setisvisible.md) | Set the temporary visibility for the chrome. This does nothing if the chrome is hidden by default and should be used to hide the chrome for things like full-screen modes with an exit button. | diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.sethelpsupporturl.md b/docs/development/core/public/kibana-plugin-public.chromestart.sethelpsupporturl.md new file mode 100644 index 00000000000000..975283ce59cb75 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromestart.sethelpsupporturl.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeStart](./kibana-plugin-public.chromestart.md) > [setHelpSupportUrl](./kibana-plugin-public.chromestart.sethelpsupporturl.md) + +## ChromeStart.setHelpSupportUrl() method + +Override the default support URL shown in the help menu + +Signature: + +```typescript +setHelpSupportUrl(url: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| url | string | | + +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-public.contextsetup.md b/docs/development/core/public/kibana-plugin-public.contextsetup.md index 2b67c7cdaf0e19..a006fa7205ca65 100644 --- a/docs/development/core/public/kibana-plugin-public.contextsetup.md +++ b/docs/development/core/public/kibana-plugin-public.contextsetup.md @@ -85,7 +85,7 @@ Say we're creating a plugin for rendering visualizations that allows new renderi export interface VizRenderContext { core: { i18n: I18nStart; - uiSettings: UISettingsClientContract; + uiSettings: IUiSettingsClient; } [contextName: string]: unknown; } diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.md b/docs/development/core/public/kibana-plugin-public.coresetup.md index f9335425fed4ca..8314bde7b95f01 100644 --- a/docs/development/core/public/kibana-plugin-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-public.coresetup.md @@ -22,5 +22,5 @@ export interface CoreSetup | [http](./kibana-plugin-public.coresetup.http.md) | HttpSetup | [HttpSetup](./kibana-plugin-public.httpsetup.md) | | [injectedMetadata](./kibana-plugin-public.coresetup.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | | [notifications](./kibana-plugin-public.coresetup.notifications.md) | NotificationsSetup | [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | -| [uiSettings](./kibana-plugin-public.coresetup.uisettings.md) | UiSettingsClientContract | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | +| [uiSettings](./kibana-plugin-public.coresetup.uisettings.md) | IUiSettingsClient | [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.uisettings.md b/docs/development/core/public/kibana-plugin-public.coresetup.uisettings.md index 78a13fccd23ed2..bf9ec12e3eea23 100644 --- a/docs/development/core/public/kibana-plugin-public.coresetup.uisettings.md +++ b/docs/development/core/public/kibana-plugin-public.coresetup.uisettings.md @@ -4,10 +4,10 @@ ## CoreSetup.uiSettings property -[UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) +[IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) Signature: ```typescript -uiSettings: UiSettingsClientContract; +uiSettings: IUiSettingsClient; ``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.md b/docs/development/core/public/kibana-plugin-public.corestart.md index 47eba78bf43e46..e561ee313f1000 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.md @@ -25,5 +25,5 @@ export interface CoreStart | [notifications](./kibana-plugin-public.corestart.notifications.md) | NotificationsStart | [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | [overlays](./kibana-plugin-public.corestart.overlays.md) | OverlayStart | [OverlayStart](./kibana-plugin-public.overlaystart.md) | | [savedObjects](./kibana-plugin-public.corestart.savedobjects.md) | SavedObjectsStart | [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | -| [uiSettings](./kibana-plugin-public.corestart.uisettings.md) | UiSettingsClientContract | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | +| [uiSettings](./kibana-plugin-public.corestart.uisettings.md) | IUiSettingsClient | [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-public.corestart.uisettings.md b/docs/development/core/public/kibana-plugin-public.corestart.uisettings.md index 1751135e01981e..2ee405591dc08d 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.uisettings.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.uisettings.md @@ -4,10 +4,10 @@ ## CoreStart.uiSettings property -[UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) +[IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) Signature: ```typescript -uiSettings: UiSettingsClientContract; +uiSettings: IUiSettingsClient; ``` diff --git a/docs/development/core/public/kibana-plugin-public.httpbody.md b/docs/development/core/public/kibana-plugin-public.httpbody.md deleted file mode 100644 index ab31f28b8dc383..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.httpbody.md +++ /dev/null @@ -1,12 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpBody](./kibana-plugin-public.httpbody.md) - -## HttpBody type - - -Signature: - -```typescript -export declare type HttpBody = BodyInit | null | any; -``` diff --git a/docs/development/core/public/kibana-plugin-public.httperrorresponse.md b/docs/development/core/public/kibana-plugin-public.httperrorresponse.md index aa669df796a09c..5b1ee898a444d6 100644 --- a/docs/development/core/public/kibana-plugin-public.httperrorresponse.md +++ b/docs/development/core/public/kibana-plugin-public.httperrorresponse.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface HttpErrorResponse extends HttpResponse +export interface HttpErrorResponse extends IHttpResponse ``` ## Properties diff --git a/docs/development/core/public/kibana-plugin-public.httpfetchoptions.asresponse.md b/docs/development/core/public/kibana-plugin-public.httpfetchoptions.asresponse.md new file mode 100644 index 00000000000000..250cf83309b3c4 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.httpfetchoptions.asresponse.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) > [asResponse](./kibana-plugin-public.httpfetchoptions.asresponse.md) + +## HttpFetchOptions.asResponse property + +When `true` the return type of [HttpHandler](./kibana-plugin-public.httphandler.md) will be an [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) with detailed request and response information. When `false`, the return type will just be the parsed response body. Defaults to `false`. + +Signature: + +```typescript +asResponse?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.httpfetchoptions.md b/docs/development/core/public/kibana-plugin-public.httpfetchoptions.md index eca29b37425e99..6a0c4a8a7f1379 100644 --- a/docs/development/core/public/kibana-plugin-public.httpfetchoptions.md +++ b/docs/development/core/public/kibana-plugin-public.httpfetchoptions.md @@ -16,6 +16,7 @@ export interface HttpFetchOptions extends HttpRequestInit | Property | Type | Description | | --- | --- | --- | +| [asResponse](./kibana-plugin-public.httpfetchoptions.asresponse.md) | boolean | When true the return type of [HttpHandler](./kibana-plugin-public.httphandler.md) will be an [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) with detailed request and response information. When false, the return type will just be the parsed response body. Defaults to false. | | [headers](./kibana-plugin-public.httpfetchoptions.headers.md) | HttpHeadersInit | Headers to send with the request. See [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md). | | [prependBasePath](./kibana-plugin-public.httpfetchoptions.prependbasepath.md) | boolean | Whether or not the request should automatically prepend the basePath. Defaults to true. | | [query](./kibana-plugin-public.httpfetchoptions.query.md) | HttpFetchQuery | The query string for an HTTP request. See [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md). | diff --git a/docs/development/core/public/kibana-plugin-public.httphandler.md b/docs/development/core/public/kibana-plugin-public.httphandler.md index 80fd1ea2e5761e..89458c4743cd6e 100644 --- a/docs/development/core/public/kibana-plugin-public.httphandler.md +++ b/docs/development/core/public/kibana-plugin-public.httphandler.md @@ -2,12 +2,12 @@ [Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpHandler](./kibana-plugin-public.httphandler.md) -## HttpHandler type +## HttpHandler interface -A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [HttpBody](./kibana-plugin-public.httpbody.md) for the response. +A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. Signature: ```typescript -export declare type HttpHandler = (path: string, options?: HttpFetchOptions) => Promise; +export interface HttpHandler ``` diff --git a/docs/development/core/public/kibana-plugin-public.httpinterceptor.response.md b/docs/development/core/public/kibana-plugin-public.httpinterceptor.response.md index ca43ea31f0e2e8..3a67dcbad3119b 100644 --- a/docs/development/core/public/kibana-plugin-public.httpinterceptor.response.md +++ b/docs/development/core/public/kibana-plugin-public.httpinterceptor.response.md @@ -9,17 +9,17 @@ Define an interceptor to be executed after a response is received. Signature: ```typescript -response?(httpResponse: HttpResponse, controller: IHttpInterceptController): Promise | InterceptedHttpResponse | void; +response?(httpResponse: IHttpResponse, controller: IHttpInterceptController): Promise | IHttpResponseInterceptorOverrides | void; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| httpResponse | HttpResponse | | +| httpResponse | IHttpResponse | | | controller | IHttpInterceptController | | Returns: -`Promise | InterceptedHttpResponse | void` +`Promise | IHttpResponseInterceptorOverrides | void` diff --git a/docs/development/core/public/kibana-plugin-public.httpinterceptor.responseerror.md b/docs/development/core/public/kibana-plugin-public.httpinterceptor.responseerror.md index b8abd50e454617..476ceba649d402 100644 --- a/docs/development/core/public/kibana-plugin-public.httpinterceptor.responseerror.md +++ b/docs/development/core/public/kibana-plugin-public.httpinterceptor.responseerror.md @@ -9,7 +9,7 @@ Define an interceptor to be executed if a response interceptor throws an error o Signature: ```typescript -responseError?(httpErrorResponse: HttpErrorResponse, controller: IHttpInterceptController): Promise | InterceptedHttpResponse | void; +responseError?(httpErrorResponse: HttpErrorResponse, controller: IHttpInterceptController): Promise | IHttpResponseInterceptorOverrides | void; ``` ## Parameters @@ -21,5 +21,5 @@ responseError?(httpErrorResponse: HttpErrorResponse, controller: IHttpInterceptC Returns: -`Promise | InterceptedHttpResponse | void` +`Promise | IHttpResponseInterceptorOverrides | void` diff --git a/docs/development/core/public/kibana-plugin-public.httpresponse.md b/docs/development/core/public/kibana-plugin-public.httpresponse.md deleted file mode 100644 index e44515cc8a1e02..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.httpresponse.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpResponse](./kibana-plugin-public.httpresponse.md) - -## HttpResponse interface - - -Signature: - -```typescript -export interface HttpResponse extends InterceptedHttpResponse -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [request](./kibana-plugin-public.httpresponse.request.md) | Readonly<Request> | | - diff --git a/docs/development/core/public/kibana-plugin-public.httpresponse.request.md b/docs/development/core/public/kibana-plugin-public.httpresponse.request.md deleted file mode 100644 index 84ab1bc7af8537..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.httpresponse.request.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpResponse](./kibana-plugin-public.httpresponse.md) > [request](./kibana-plugin-public.httpresponse.request.md) - -## HttpResponse.request property - -Signature: - -```typescript -request: Readonly; -``` diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponse.body.md b/docs/development/core/public/kibana-plugin-public.ihttpresponse.body.md new file mode 100644 index 00000000000000..2f8710ccdc60ef --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponse.body.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) > [body](./kibana-plugin-public.ihttpresponse.body.md) + +## IHttpResponse.body property + +Parsed body received, may be undefined if there was an error. + +Signature: + +```typescript +readonly body?: TResponseBody; +``` diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponse.md b/docs/development/core/public/kibana-plugin-public.ihttpresponse.md new file mode 100644 index 00000000000000..5ddce0ba2d0f14 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponse.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) + +## IHttpResponse interface + + +Signature: + +```typescript +export interface IHttpResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [body](./kibana-plugin-public.ihttpresponse.body.md) | TResponseBody | Parsed body received, may be undefined if there was an error. | +| [request](./kibana-plugin-public.ihttpresponse.request.md) | Readonly<Request> | Raw request sent to Kibana server. | +| [response](./kibana-plugin-public.ihttpresponse.response.md) | Readonly<Response> | Raw response received, may be undefined if there was an error. | + diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponse.request.md b/docs/development/core/public/kibana-plugin-public.ihttpresponse.request.md new file mode 100644 index 00000000000000..12e5405eb5ed43 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponse.request.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) > [request](./kibana-plugin-public.ihttpresponse.request.md) + +## IHttpResponse.request property + +Raw request sent to Kibana server. + +Signature: + +```typescript +readonly request: Readonly; +``` diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponse.response.md b/docs/development/core/public/kibana-plugin-public.ihttpresponse.response.md new file mode 100644 index 00000000000000..9d0b4b59a638d7 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponse.response.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) > [response](./kibana-plugin-public.ihttpresponse.response.md) + +## IHttpResponse.response property + +Raw response received, may be undefined if there was an error. + +Signature: + +```typescript +readonly response?: Readonly; +``` diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.body.md b/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.body.md new file mode 100644 index 00000000000000..36fcfb390617c3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.body.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) > [body](./kibana-plugin-public.ihttpresponseinterceptoroverrides.body.md) + +## IHttpResponseInterceptorOverrides.body property + +Parsed body received, may be undefined if there was an error. + +Signature: + +```typescript +readonly body?: TResponseBody; +``` diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.md b/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.md new file mode 100644 index 00000000000000..44f067c429e987 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) + +## IHttpResponseInterceptorOverrides interface + +Properties that can be returned by HttpInterceptor.request to override the response. + +Signature: + +```typescript +export interface IHttpResponseInterceptorOverrides +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [body](./kibana-plugin-public.ihttpresponseinterceptoroverrides.body.md) | TResponseBody | Parsed body received, may be undefined if there was an error. | +| [response](./kibana-plugin-public.ihttpresponseinterceptoroverrides.response.md) | Readonly<Response> | Raw response received, may be undefined if there was an error. | + diff --git a/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.response.md b/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.response.md new file mode 100644 index 00000000000000..bcba996645ba6d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ihttpresponseinterceptoroverrides.response.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) > [response](./kibana-plugin-public.ihttpresponseinterceptoroverrides.response.md) + +## IHttpResponseInterceptorOverrides.response property + +Raw response received, may be undefined if there was an error. + +Signature: + +```typescript +readonly response?: Readonly; +``` diff --git a/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.body.md b/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.body.md deleted file mode 100644 index fc6d34c0b74f2a..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.body.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [InterceptedHttpResponse](./kibana-plugin-public.interceptedhttpresponse.md) > [body](./kibana-plugin-public.interceptedhttpresponse.body.md) - -## InterceptedHttpResponse.body property - -Signature: - -```typescript -body?: HttpBody; -``` diff --git a/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.md b/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.md deleted file mode 100644 index c4a7f4d6b2afaf..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [InterceptedHttpResponse](./kibana-plugin-public.interceptedhttpresponse.md) - -## InterceptedHttpResponse interface - - -Signature: - -```typescript -export interface InterceptedHttpResponse -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [body](./kibana-plugin-public.interceptedhttpresponse.body.md) | HttpBody | | -| [response](./kibana-plugin-public.interceptedhttpresponse.response.md) | Response | | - diff --git a/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.response.md b/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.response.md deleted file mode 100644 index dceb55113ee784..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.interceptedhttpresponse.response.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [InterceptedHttpResponse](./kibana-plugin-public.interceptedhttpresponse.md) > [response](./kibana-plugin-public.interceptedhttpresponse.response.md) - -## InterceptedHttpResponse.response property - -Signature: - -```typescript -response?: Response; -``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.get.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.get.md new file mode 100644 index 00000000000000..8d14a10951a925 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [get](./kibana-plugin-public.iuisettingsclient.get.md) + +## IUiSettingsClient.get property + +Gets the value for a specific uiSetting. If this setting has no user-defined value then the `defaultOverride` parameter is returned (and parsed if setting is of type "json" or "number). If the parameter is not defined and the key is not registered by any plugin then an error is thrown, otherwise reads the default value defined by a plugin. + +Signature: + +```typescript +get: (key: string, defaultOverride?: T) => T; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.get_.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.get_.md new file mode 100644 index 00000000000000..b7680b769f303f --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.get_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [get$](./kibana-plugin-public.iuisettingsclient.get_.md) + +## IUiSettingsClient.get$ property + +Gets an observable of the current value for a config key, and all updates to that config key in the future. Providing a `defaultOverride` argument behaves the same as it does in \#get() + +Signature: + +```typescript +get$: (key: string, defaultOverride?: T) => Observable; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getall.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getall.md new file mode 100644 index 00000000000000..b767a8ff603c86 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getall.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [getAll](./kibana-plugin-public.iuisettingsclient.getall.md) + +## IUiSettingsClient.getAll property + +Gets the metadata about all uiSettings, including the type, default value, and user value for each key. + +Signature: + +```typescript +getAll: () => Readonly>; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getsaved_.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getsaved_.md new file mode 100644 index 00000000000000..a4ddb9abcba979 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getsaved_.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [getSaved$](./kibana-plugin-public.iuisettingsclient.getsaved_.md) + +## IUiSettingsClient.getSaved$ property + +Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. + +Signature: + +```typescript +getSaved$: () => Observable<{ + key: string; + newValue: T; + oldValue: T; + }>; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getupdate_.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getupdate_.md new file mode 100644 index 00000000000000..cec5bc096cf02c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getupdate_.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [getUpdate$](./kibana-plugin-public.iuisettingsclient.getupdate_.md) + +## IUiSettingsClient.getUpdate$ property + +Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. + +Signature: + +```typescript +getUpdate$: () => Observable<{ + key: string; + newValue: T; + oldValue: T; + }>; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getupdateerrors_.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getupdateerrors_.md new file mode 100644 index 00000000000000..2fbcaac03e2bb8 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.getupdateerrors_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [getUpdateErrors$](./kibana-plugin-public.iuisettingsclient.getupdateerrors_.md) + +## IUiSettingsClient.getUpdateErrors$ property + +Returns an Observable that notifies subscribers of each error while trying to update the settings, containing the actual Error class. + +Signature: + +```typescript +getUpdateErrors$: () => Observable; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.iscustom.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.iscustom.md new file mode 100644 index 00000000000000..30de59c066ee3b --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.iscustom.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [isCustom](./kibana-plugin-public.iuisettingsclient.iscustom.md) + +## IUiSettingsClient.isCustom property + +Returns true if the setting wasn't registered by any plugin, but was either added directly via `set()`, or is an unknown setting found in the uiSettings saved object + +Signature: + +```typescript +isCustom: (key: string) => boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isdeclared.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isdeclared.md new file mode 100644 index 00000000000000..1ffcb61967e8ad --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isdeclared.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [isDeclared](./kibana-plugin-public.iuisettingsclient.isdeclared.md) + +## IUiSettingsClient.isDeclared property + +Returns true if the key is a "known" uiSetting, meaning it is either registered by any plugin or was previously added as a custom setting via the `set()` method. + +Signature: + +```typescript +isDeclared: (key: string) => boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isdefault.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isdefault.md new file mode 100644 index 00000000000000..d61367c9841d42 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [isDefault](./kibana-plugin-public.iuisettingsclient.isdefault.md) + +## IUiSettingsClient.isDefault property + +Returns true if the setting has no user-defined value or is unknown + +Signature: + +```typescript +isDefault: (key: string) => boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isoverridden.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isoverridden.md new file mode 100644 index 00000000000000..5749e1db1fe430 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.isoverridden.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [isOverridden](./kibana-plugin-public.iuisettingsclient.isoverridden.md) + +## IUiSettingsClient.isOverridden property + +Shows whether the uiSettings value set by the user. + +Signature: + +```typescript +isOverridden: (key: string) => boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.md new file mode 100644 index 00000000000000..4183a30806d9a2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) + +## IUiSettingsClient interface + +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. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) + +Signature: + +```typescript +export interface IUiSettingsClient +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [get](./kibana-plugin-public.iuisettingsclient.get.md) | <T = any>(key: string, defaultOverride?: T) => T | Gets the value for a specific uiSetting. If this setting has no user-defined value then the defaultOverride parameter is returned (and parsed if setting is of type "json" or "number). If the parameter is not defined and the key is not registered by any plugin then an error is thrown, otherwise reads the default value defined by a plugin. | +| [get$](./kibana-plugin-public.iuisettingsclient.get_.md) | <T = any>(key: string, defaultOverride?: T) => Observable<T> | Gets an observable of the current value for a config key, and all updates to that config key in the future. Providing a defaultOverride argument behaves the same as it does in \#get() | +| [getAll](./kibana-plugin-public.iuisettingsclient.getall.md) | () => Readonly<Record<string, UiSettingsParams & UserProvidedValues>> | Gets the metadata about all uiSettings, including the type, default value, and user value for each key. | +| [getSaved$](./kibana-plugin-public.iuisettingsclient.getsaved_.md) | <T = any>() => Observable<{
key: string;
newValue: T;
oldValue: T;
}> | Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. | +| [getUpdate$](./kibana-plugin-public.iuisettingsclient.getupdate_.md) | <T = any>() => Observable<{
key: string;
newValue: T;
oldValue: T;
}> | Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. | +| [getUpdateErrors$](./kibana-plugin-public.iuisettingsclient.getupdateerrors_.md) | () => Observable<Error> | Returns an Observable that notifies subscribers of each error while trying to update the settings, containing the actual Error class. | +| [isCustom](./kibana-plugin-public.iuisettingsclient.iscustom.md) | (key: string) => boolean | Returns true if the setting wasn't registered by any plugin, but was either added directly via set(), or is an unknown setting found in the uiSettings saved object | +| [isDeclared](./kibana-plugin-public.iuisettingsclient.isdeclared.md) | (key: string) => boolean | Returns true if the key is a "known" uiSetting, meaning it is either registered by any plugin or was previously added as a custom setting via the set() method. | +| [isDefault](./kibana-plugin-public.iuisettingsclient.isdefault.md) | (key: string) => boolean | Returns true if the setting has no user-defined value or is unknown | +| [isOverridden](./kibana-plugin-public.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | +| [overrideLocalDefault](./kibana-plugin-public.iuisettingsclient.overridelocaldefault.md) | (key: string, newDefault: any) => void | Overrides the default value for a setting in this specific browser tab. If the page is reloaded the default override is lost. | +| [remove](./kibana-plugin-public.iuisettingsclient.remove.md) | (key: string) => Promise<boolean> | Removes the user-defined value for a setting, causing it to revert to the default. This method behaves the same as calling set(key, null), including the synchronization, custom setting, and error behavior of that method. | +| [set](./kibana-plugin-public.iuisettingsclient.set.md) | (key: string, value: any) => Promise<boolean> | Sets the value for a uiSetting. If the setting is not registered by any plugin it will be stored as a custom setting. The new value will be synchronously available via the get() method and sent to the server in the background. If the request to the server fails then a updateErrors$ will be notified and the setting will be reverted to its value before set() was called. | + diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.overridelocaldefault.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.overridelocaldefault.md new file mode 100644 index 00000000000000..d7e7c01876654a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.overridelocaldefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [overrideLocalDefault](./kibana-plugin-public.iuisettingsclient.overridelocaldefault.md) + +## IUiSettingsClient.overrideLocalDefault property + +Overrides the default value for a setting in this specific browser tab. If the page is reloaded the default override is lost. + +Signature: + +```typescript +overrideLocalDefault: (key: string, newDefault: any) => void; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.remove.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.remove.md new file mode 100644 index 00000000000000..c2171e5c883f83 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.remove.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [remove](./kibana-plugin-public.iuisettingsclient.remove.md) + +## IUiSettingsClient.remove property + +Removes the user-defined value for a setting, causing it to revert to the default. This method behaves the same as calling `set(key, null)`, including the synchronization, custom setting, and error behavior of that method. + +Signature: + +```typescript +remove: (key: string) => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-public.iuisettingsclient.set.md b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.set.md new file mode 100644 index 00000000000000..d9e62eec4cf089 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.iuisettingsclient.set.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) > [set](./kibana-plugin-public.iuisettingsclient.set.md) + +## IUiSettingsClient.set property + +Sets the value for a uiSetting. If the setting is not registered by any plugin it will be stored as a custom setting. The new value will be synchronously available via the `get()` method and sent to the server in the background. If the request to the server fails then a updateErrors$ will be notified and the setting will be reverted to its value before `set()` was called. + +Signature: + +```typescript +set: (key: string, value: any) => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 22794ca9455401..22b6f7faf2daab 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -17,7 +17,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. | | [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md).It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. | | [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. | -| [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | | ## Interfaces @@ -53,10 +52,10 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | | | [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md). | | [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | | +| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. | | [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | | | [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md). | | [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)s. | -| [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 | @@ -64,7 +63,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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) | | | [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md). | -| [InterceptedHttpResponse](./kibana-plugin-public.interceptedhttpresponse.md) | | +| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | | +| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | +| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.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. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | | [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | @@ -108,8 +109,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | | [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | | [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | -| [HttpBody](./kibana-plugin-public.httpbody.md) | | -| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [HttpBody](./kibana-plugin-public.httpbody.md) for the response. | | [HttpSetup](./kibana-plugin-public.httpsetup.md) | See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | | [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | | [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | @@ -126,6 +125,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) | 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) | | [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) | diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md index cecceb04240e60..1ce18834f53196 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md @@ -9,5 +9,5 @@ Search for objects Signature: ```typescript -find: (options: Pick) => Promise>; +find: (options: Pick) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index c4ceb47f66e1bb..6033c667c1866c 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -20,7 +20,7 @@ export declare class SavedObjectsClient | [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | | [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | | [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | ## Methods diff --git a/docs/development/core/public/kibana-plugin-public.toastsapi._constructor_.md b/docs/development/core/public/kibana-plugin-public.toastsapi._constructor_.md index 31a16403a41a17..2b5ce41de8ecee 100644 --- a/docs/development/core/public/kibana-plugin-public.toastsapi._constructor_.md +++ b/docs/development/core/public/kibana-plugin-public.toastsapi._constructor_.md @@ -10,7 +10,7 @@ Constructs a new instance of the `ToastsApi` class ```typescript constructor(deps: { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; }); ``` @@ -18,5 +18,5 @@ constructor(deps: { | Parameter | Type | Description | | --- | --- | --- | -| deps | {
uiSettings: UiSettingsClientContract;
} | | +| deps | {
uiSettings: IUiSettingsClient;
} | | diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient._constructor_.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient._constructor_.md deleted file mode 100644 index a7698fe61e1620..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [(constructor)](./kibana-plugin-public.uisettingsclient._constructor_.md) - -## UiSettingsClient.(constructor) - -Constructs a new instance of the `UiSettingsClient` class - -Signature: - -```typescript -constructor(params: UiSettingsClientParams); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| params | UiSettingsClientParams | | - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.get.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.get.md deleted file mode 100644 index 03fa38575b6b86..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.get.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [get](./kibana-plugin-public.uisettingsclient.get.md) - -## UiSettingsClient.get() method - -Gets the value for a specific uiSetting. If this setting has no user-defined value then the `defaultOverride` parameter is returned (and parsed if setting is of type "json" or "number). If the parameter is not defined and the key is not defined by a uiSettingDefaults then an error is thrown, otherwise the default is read from the uiSettingDefaults. - -Signature: - -```typescript -get(key: string, defaultOverride?: any): any; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | -| defaultOverride | any | | - -Returns: - -`any` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.get_.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.get_.md deleted file mode 100644 index 6a515a8f514a24..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.get_.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [get$](./kibana-plugin-public.uisettingsclient.get_.md) - -## UiSettingsClient.get$() method - -Gets an observable of the current value for a config key, and all updates to that config key in the future. Providing a `defaultOverride` argument behaves the same as it does in \#get() - -Signature: - -```typescript -get$(key: string, defaultOverride?: any): Rx.Observable; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | -| defaultOverride | any | | - -Returns: - -`Rx.Observable` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md deleted file mode 100644 index 06daf8e8151cd3..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [getAll](./kibana-plugin-public.uisettingsclient.getall.md) - -## UiSettingsClient.getAll() method - -Gets the metadata about all uiSettings, including the type, default value, and user value for each key. - -Signature: - -```typescript -getAll(): Record>; -``` -Returns: - -`Record>` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getsaved_.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getsaved_.md deleted file mode 100644 index 9e46b286c4009c..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getsaved_.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [getSaved$](./kibana-plugin-public.uisettingsclient.getsaved_.md) - -## UiSettingsClient.getSaved$() method - -Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. - -Signature: - -```typescript -getSaved$(): Rx.Observable<{ - key: string; - newValue: any; - oldValue: any; - }>; -``` -Returns: - -`Rx.Observable<{ - key: string; - newValue: any; - oldValue: any; - }>` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getupdate_.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getupdate_.md deleted file mode 100644 index b9cab6e87a9965..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getupdate_.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [getUpdate$](./kibana-plugin-public.uisettingsclient.getupdate_.md) - -## UiSettingsClient.getUpdate$() method - -Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. - -Signature: - -```typescript -getUpdate$(): Rx.Observable<{ - key: string; - newValue: any; - oldValue: any; - }>; -``` -Returns: - -`Rx.Observable<{ - key: string; - newValue: any; - oldValue: any; - }>` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getupdateerrors_.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getupdateerrors_.md deleted file mode 100644 index ada2a56ac8db67..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getupdateerrors_.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [getUpdateErrors$](./kibana-plugin-public.uisettingsclient.getupdateerrors_.md) - -## UiSettingsClient.getUpdateErrors$() method - -Returns an Observable that notifies subscribers of each error while trying to update the settings, containing the actual Error class. - -Signature: - -```typescript -getUpdateErrors$(): Rx.Observable; -``` -Returns: - -`Rx.Observable` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.iscustom.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.iscustom.md deleted file mode 100644 index 8855e39d7e8f3c..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.iscustom.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [isCustom](./kibana-plugin-public.uisettingsclient.iscustom.md) - -## UiSettingsClient.isCustom() method - -Returns true if the setting is not a part of the uiSettingDefaults, but was either added directly via `set()`, or is an unknown setting found in the uiSettings saved object - -Signature: - -```typescript -isCustom(key: string): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -`boolean` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.isdeclared.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.isdeclared.md deleted file mode 100644 index 61b9d3a11a1afb..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.isdeclared.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [isDeclared](./kibana-plugin-public.uisettingsclient.isdeclared.md) - -## UiSettingsClient.isDeclared() method - -Returns true if the key is a "known" uiSetting, meaning it is either defined in the uiSettingDefaults or was previously added as a custom setting via the `set()` method. - -Signature: - -```typescript -isDeclared(key: string): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -`boolean` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.isdefault.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.isdefault.md deleted file mode 100644 index 09a04f99e82850..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.isdefault.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [isDefault](./kibana-plugin-public.uisettingsclient.isdefault.md) - -## UiSettingsClient.isDefault() method - -Returns true if the setting has no user-defined value or is unknown - -Signature: - -```typescript -isDefault(key: string): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -`boolean` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.isoverridden.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.isoverridden.md deleted file mode 100644 index 5311ffbf40d953..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.isoverridden.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [isOverridden](./kibana-plugin-public.uisettingsclient.isoverridden.md) - -## UiSettingsClient.isOverridden() method - -Returns true if a settings value is overridden by the server. When a setting is overridden its value can not be changed via `set()` or `remove()`. - -Signature: - -```typescript -isOverridden(key: string): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -`boolean` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.md deleted file mode 100644 index 642e6db144f095..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.md +++ /dev/null @@ -1,38 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) - -## UiSettingsClient class - - -Signature: - -```typescript -export declare class UiSettingsClient -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(params)](./kibana-plugin-public.uisettingsclient._constructor_.md) | | Constructs a new instance of the UiSettingsClient class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [get(key, defaultOverride)](./kibana-plugin-public.uisettingsclient.get.md) | | Gets the value for a specific uiSetting. If this setting has no user-defined value then the defaultOverride parameter is returned (and parsed if setting is of type "json" or "number). If the parameter is not defined and the key is not defined by a uiSettingDefaults then an error is thrown, otherwise the default is read from the uiSettingDefaults. | -| [get$(key, defaultOverride)](./kibana-plugin-public.uisettingsclient.get_.md) | | Gets an observable of the current value for a config key, and all updates to that config key in the future. Providing a defaultOverride argument behaves the same as it does in \#get() | -| [getAll()](./kibana-plugin-public.uisettingsclient.getall.md) | | Gets the metadata about all uiSettings, including the type, default value, and user value for each key. | -| [getSaved$()](./kibana-plugin-public.uisettingsclient.getsaved_.md) | | Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. | -| [getUpdate$()](./kibana-plugin-public.uisettingsclient.getupdate_.md) | | Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. | -| [getUpdateErrors$()](./kibana-plugin-public.uisettingsclient.getupdateerrors_.md) | | Returns an Observable that notifies subscribers of each error while trying to update the settings, containing the actual Error class. | -| [isCustom(key)](./kibana-plugin-public.uisettingsclient.iscustom.md) | | Returns true if the setting is not a part of the uiSettingDefaults, but was either added directly via set(), or is an unknown setting found in the uiSettings saved object | -| [isDeclared(key)](./kibana-plugin-public.uisettingsclient.isdeclared.md) | | Returns true if the key is a "known" uiSetting, meaning it is either defined in the uiSettingDefaults or was previously added as a custom setting via the set() method. | -| [isDefault(key)](./kibana-plugin-public.uisettingsclient.isdefault.md) | | Returns true if the setting has no user-defined value or is unknown | -| [isOverridden(key)](./kibana-plugin-public.uisettingsclient.isoverridden.md) | | Returns true if a settings value is overridden by the server. When a setting is overridden its value can not be changed via set() or remove(). | -| [overrideLocalDefault(key, newDefault)](./kibana-plugin-public.uisettingsclient.overridelocaldefault.md) | | Overrides the default value for a setting in this specific browser tab. If the page is reloaded the default override is lost. | -| [remove(key)](./kibana-plugin-public.uisettingsclient.remove.md) | | Removes the user-defined value for a setting, causing it to revert to the default. This method behaves the same as calling set(key, null), including the synchronization, custom setting, and error behavior of that method. | -| [set(key, val)](./kibana-plugin-public.uisettingsclient.set.md) | | Sets the value for a uiSetting. If the setting is not defined in the uiSettingDefaults it will be stored as a custom setting. The new value will be synchronously available via the get() method and sent to the server in the background. If the request to the server fails then a toast notification will be displayed and the setting will be reverted it its value before set() was called. | -| [stop()](./kibana-plugin-public.uisettingsclient.stop.md) | | Prepares the uiSettingsClient to be discarded, completing any update$ observables that have been created. | - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.overridelocaldefault.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.overridelocaldefault.md deleted file mode 100644 index b94fe72fff1029..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.overridelocaldefault.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [overrideLocalDefault](./kibana-plugin-public.uisettingsclient.overridelocaldefault.md) - -## UiSettingsClient.overrideLocalDefault() method - -Overrides the default value for a setting in this specific browser tab. If the page is reloaded the default override is lost. - -Signature: - -```typescript -overrideLocalDefault(key: string, newDefault: any): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | -| newDefault | any | | - -Returns: - -`void` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.remove.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.remove.md deleted file mode 100644 index 3d07e754496395..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.remove.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [remove](./kibana-plugin-public.uisettingsclient.remove.md) - -## UiSettingsClient.remove() method - -Removes the user-defined value for a setting, causing it to revert to the default. This method behaves the same as calling `set(key, null)`, including the synchronization, custom setting, and error behavior of that method. - -Signature: - -```typescript -remove(key: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -`Promise` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.set.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.set.md deleted file mode 100644 index ad1d97b8fe9b3a..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.set.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [set](./kibana-plugin-public.uisettingsclient.set.md) - -## UiSettingsClient.set() method - -Sets the value for a uiSetting. If the setting is not defined in the uiSettingDefaults it will be stored as a custom setting. The new value will be synchronously available via the `get()` method and sent to the server in the background. If the request to the server fails then a toast notification will be displayed and the setting will be reverted it its value before `set()` was called. - -Signature: - -```typescript -set(key: string, val: any): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | -| val | any | | - -Returns: - -`Promise` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.stop.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.stop.md deleted file mode 100644 index 215a94544d2d0d..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.stop.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) > [stop](./kibana-plugin-public.uisettingsclient.stop.md) - -## UiSettingsClient.stop() method - -Prepares the uiSettingsClient to be discarded, completing any update$ observables that have been created. - -Signature: - -```typescript -stop(): void; -``` -Returns: - -`void` - diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md b/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md deleted file mode 100644 index 7173386d882652..00000000000000 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) - -## UiSettingsClientContract type - -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: - -```typescript -export declare type UiSettingsClientContract = PublicMethodsOf; -``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.get.md b/docs/development/core/server/kibana-plugin-server.basepath.get.md index 2b3b6c899e8ded..6ef7022f10e624 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.get.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.get.md @@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request. Signature: ```typescript -get: (request: KibanaRequest | LegacyRequest) => string; +get: (request: KibanaRequest | LegacyRequest) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.md b/docs/development/core/server/kibana-plugin-server.basepath.md index 478e29696966cd..77f50abc60369e 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.md @@ -16,11 +16,11 @@ export declare class BasePath | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | +| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | | [prepend](./kibana-plugin-server.basepath.prepend.md) | | (path: string) => string | Prepends path with the basePath. | | [remove](./kibana-plugin-server.basepath.remove.md) | | (path: string) => string | Removes the prepended basePath from the path. | | [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | string | returns the server's basePathSee [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request | -| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | +| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | ## Remarks diff --git a/docs/development/core/server/kibana-plugin-server.basepath.set.md b/docs/development/core/server/kibana-plugin-server.basepath.set.md index 1272a134ef5c44..56a7f644d34ccc 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.set.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.set.md @@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request. Signature: ```typescript -set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md b/docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md new file mode 100644 index 00000000000000..4eb012c78f0cbc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.catalogue.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) > [catalogue](./kibana-plugin-server.capabilities.catalogue.md) + +## Capabilities.catalogue property + +Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. + +Signature: + +```typescript +catalogue: Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.management.md b/docs/development/core/server/kibana-plugin-server.capabilities.management.md new file mode 100644 index 00000000000000..d917c81dc37206 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.management.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) > [management](./kibana-plugin-server.capabilities.management.md) + +## Capabilities.management property + +Management section capabilities. + +Signature: + +```typescript +management: { + [sectionId: string]: Record; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.md b/docs/development/core/server/kibana-plugin-server.capabilities.md new file mode 100644 index 00000000000000..031282b9733acc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) + +## Capabilities interface + +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. + +Signature: + +```typescript +export interface Capabilities +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [catalogue](./kibana-plugin-server.capabilities.catalogue.md) | Record<string, boolean> | Catalogue capabilities. Catalogue entries drive the visibility of the Kibana homepage options. | +| [management](./kibana-plugin-server.capabilities.management.md) | {
[sectionId: string]: Record<string, boolean>;
} | Management section capabilities. | +| [navLinks](./kibana-plugin-server.capabilities.navlinks.md) | Record<string, boolean> | Navigation link capabilities. | + diff --git a/docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md b/docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md new file mode 100644 index 00000000000000..a1612ea840fbf7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilities.navlinks.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Capabilities](./kibana-plugin-server.capabilities.md) > [navLinks](./kibana-plugin-server.capabilities.navlinks.md) + +## Capabilities.navLinks property + +Navigation link capabilities. + +Signature: + +```typescript +navLinks: Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md new file mode 100644 index 00000000000000..66e5d256ada66b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesprovider.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) + +## CapabilitiesProvider type + +See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +Signature: + +```typescript +export declare type CapabilitiesProvider = () => Partial; +``` diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md new file mode 100644 index 00000000000000..27c42fe75e7517 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +## CapabilitiesSetup interface + +APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application. + +Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the `registerProvider` method. + +Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the `registerSwitcher` method. + +Refers to the methods documentation for complete description and examples. + +Signature: + +```typescript +export interface CapabilitiesSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [registerProvider(provider)](./kibana-plugin-server.capabilitiessetup.registerprovider.md) | Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used to provide [Capabilities](./kibana-plugin-server.capabilities.md) when resolving them. | +| [registerSwitcher(switcher)](./kibana-plugin-server.capabilitiessetup.registerswitcher.md) | Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used to change the default state of the [Capabilities](./kibana-plugin-server.capabilities.md) entries when resolving them.A capabilities switcher can only change the state of existing capabilities. Capabilities added or removed when invoking the switcher will be ignored. | + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md new file mode 100644 index 00000000000000..750913ee35895b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerprovider.md @@ -0,0 +1,46 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerProvider](./kibana-plugin-server.capabilitiessetup.registerprovider.md) + +## CapabilitiesSetup.registerProvider() method + +Register a [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) to be used to provide [Capabilities](./kibana-plugin-server.capabilities.md) when resolving them. + +Signature: + +```typescript +registerProvider(provider: CapabilitiesProvider): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| provider | CapabilitiesProvider | | + +Returns: + +`void` + +## Example + +How to register a plugin's capabilities during setup + +```ts +// my-plugin/server/plugin.ts +public setup(core: CoreSetup, deps: {}) { + core.capabilities.registerProvider(() => { + return { + catalogue: { + myPlugin: true, + }, + myPlugin: { + someFeature: true, + featureDisabledByDefault: false, + }, + } + }); +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md new file mode 100644 index 00000000000000..fbaa2959c635c2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiessetup.registerswitcher.md @@ -0,0 +1,47 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) > [registerSwitcher](./kibana-plugin-server.capabilitiessetup.registerswitcher.md) + +## CapabilitiesSetup.registerSwitcher() method + +Register a [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) to be used to change the default state of the [Capabilities](./kibana-plugin-server.capabilities.md) entries when resolving them. + +A capabilities switcher can only change the state of existing capabilities. Capabilities added or removed when invoking the switcher will be ignored. + +Signature: + +```typescript +registerSwitcher(switcher: CapabilitiesSwitcher): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| switcher | CapabilitiesSwitcher | | + +Returns: + +`void` + +## Example + +How to restrict some capabilities + +```ts +// my-plugin/server/plugin.ts +public setup(core: CoreSetup, deps: {}) { + core.capabilities.registerSwitcher((request, capabilities) => { + if(myPluginApi.shouldRestrictSomePluginBecauseOf(request)) { + return { + somePlugin: { + featureEnabledByDefault: false // `featureEnabledByDefault` will be disabled. All other capabilities will remain unchanged. + } + } + } + return {}; // All capabilities will remain unchanged. + }); +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md new file mode 100644 index 00000000000000..55cc1aed76b5b5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) + +## CapabilitiesStart interface + +APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). + +Signature: + +```typescript +export interface CapabilitiesStart +``` + +## Methods + +| Method | Description | +| --- | --- | +| [resolveCapabilities(request)](./kibana-plugin-server.capabilitiesstart.resolvecapabilities.md) | Resolve the [Capabilities](./kibana-plugin-server.capabilities.md) to be used for given request | + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md new file mode 100644 index 00000000000000..95b751dd4fc95f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesstart.resolvecapabilities.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) > [resolveCapabilities](./kibana-plugin-server.capabilitiesstart.resolvecapabilities.md) + +## CapabilitiesStart.resolveCapabilities() method + +Resolve the [Capabilities](./kibana-plugin-server.capabilities.md) to be used for given request + +Signature: + +```typescript +resolveCapabilities(request: KibanaRequest): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | KibanaRequest | | + +Returns: + +`Promise` + diff --git a/docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md b/docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md new file mode 100644 index 00000000000000..dd6af543768965 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.capabilitiesswitcher.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) + +## CapabilitiesSwitcher type + +See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +Signature: + +```typescript +export declare type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.contextsetup.md b/docs/development/core/server/kibana-plugin-server.contextsetup.md index 67504faf0534ab..1f285efe92b682 100644 --- a/docs/development/core/server/kibana-plugin-server.contextsetup.md +++ b/docs/development/core/server/kibana-plugin-server.contextsetup.md @@ -85,7 +85,7 @@ Say we're creating a plugin for rendering visualizations that allows new renderi export interface VizRenderContext { core: { i18n: I18nStart; - uiSettings: UISettingsClientContract; + uiSettings: IUiSettingsClient; } [contextName: string]: unknown; } diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md b/docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md new file mode 100644 index 00000000000000..fe50347d97e3c4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.capabilities.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [capabilities](./kibana-plugin-server.coresetup.capabilities.md) + +## CoreSetup.capabilities property + +[CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) + +Signature: + +```typescript +capabilities: CapabilitiesSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index c51459bc41a434..3886b6e05e6570 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -16,8 +16,10 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | +| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | | [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) | +| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.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.savedobjects.md b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md new file mode 100644 index 00000000000000..96acc1ffce1944 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) + +## CoreSetup.savedObjects property + +[SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +Signature: + +```typescript +savedObjects: SavedObjectsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.corestart.capabilities.md b/docs/development/core/server/kibana-plugin-server.corestart.capabilities.md new file mode 100644 index 00000000000000..03930d367ee75f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.corestart.capabilities.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [capabilities](./kibana-plugin-server.corestart.capabilities.md) + +## CoreStart.capabilities property + +[CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) + +Signature: + +```typescript +capabilities: CapabilitiesStart; +``` diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index da80ae8be93afc..e523717a37ac8e 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -11,3 +11,11 @@ Context passed to the plugins `start` method. ```typescript export interface CoreStart ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [capabilities](./kibana-plugin-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | +| [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md new file mode 100644 index 00000000000000..531b04e9eed07a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) + +## CoreStart.savedObjects property + +[SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +Signature: + +```typescript +savedObjects: SavedObjectsServiceStart; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index dba0ad8c8560cb..25eebf1c06d010 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -23,6 +23,7 @@ export interface HttpServiceSetup | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | | [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | | [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | ## Example diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md new file mode 100644 index 00000000000000..9f0eaae8830e10 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) + +## HttpServiceSetup.registerOnPreResponse property + +To define custom logic to perform for the server response. + +Signature: + +```typescript +registerOnPreResponse: (handler: OnPreResponseHandler) => void; +``` + +## Remarks + +Doesn't provide the whole response object. Supports extending response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md). + diff --git a/docs/development/core/server/kibana-plugin-server.irouter.delete.md b/docs/development/core/server/kibana-plugin-server.irouter.delete.md index 5202e0cfd5ebb7..a479c03ecede3a 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.delete.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.delete.md @@ -9,5 +9,5 @@ Register a route handler for `DELETE` request. Signature: ```typescript -delete: RouteRegistrar; +delete: RouteRegistrar<'delete'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.get.md b/docs/development/core/server/kibana-plugin-server.irouter.get.md index 32552a49cb999a..0d52ef26f008c7 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.get.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.get.md @@ -9,5 +9,5 @@ Register a route handler for `GET` request. Signature: ```typescript -get: RouteRegistrar; +get: RouteRegistrar<'get'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.md b/docs/development/core/server/kibana-plugin-server.irouter.md index b5d3c893d745dc..73e96191e02e71 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.md @@ -16,10 +16,11 @@ export interface IRouter | Property | Type | Description | | --- | --- | --- | -| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar | Register a route handler for DELETE request. | -| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar | Register a route handler for GET request. | +| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar<'delete'> | Register a route handler for DELETE request. | +| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar<'get'> | Register a route handler for GET request. | | [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | -| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar | Register a route handler for POST request. | -| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar | Register a route handler for PUT request. | +| [patch](./kibana-plugin-server.irouter.patch.md) | RouteRegistrar<'patch'> | Register a route handler for PATCH request. | +| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar<'post'> | Register a route handler for POST request. | +| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar<'put'> | Register a route handler for PUT request. | | [routerPath](./kibana-plugin-server.irouter.routerpath.md) | string | Resulted path | diff --git a/docs/development/core/server/kibana-plugin-server.irouter.patch.md b/docs/development/core/server/kibana-plugin-server.irouter.patch.md new file mode 100644 index 00000000000000..460f1b9d23640f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.irouter.patch.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [patch](./kibana-plugin-server.irouter.patch.md) + +## IRouter.patch property + +Register a route handler for `PATCH` request. + +Signature: + +```typescript +patch: RouteRegistrar<'patch'>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.post.md b/docs/development/core/server/kibana-plugin-server.irouter.post.md index cd655c9ce0dc8b..a2ac27ebc731ae 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.post.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.post.md @@ -9,5 +9,5 @@ Register a route handler for `POST` request. Signature: ```typescript -post: RouteRegistrar; +post: RouteRegistrar<'post'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.put.md b/docs/development/core/server/kibana-plugin-server.irouter.put.md index e553d4b79dd2b3..219c5d8805661f 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.put.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.put.md @@ -9,5 +9,5 @@ Register a route handler for `PUT` request. Signature: ```typescript -put: RouteRegistrar; +put: RouteRegistrar<'put'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md new file mode 100644 index 00000000000000..7863d1b0ca49dd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) + +## ISavedObjectsRepository type + +See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) + +Signature: + +```typescript +export declare type ISavedObjectsRepository = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md index 0ec3ac45c6cb5f..a73061f457a4bb 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md @@ -9,5 +9,5 @@ Retrieves uiSettings values set by the user with fallbacks to default values if Signature: ```typescript -get: (key: string) => Promise; +get: (key: string) => Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md index d6765a5e5407e4..600116b86d1c03 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md @@ -9,5 +9,5 @@ Retrieves a set of all uiSettings values set by the user with fallbacks to defau Signature: ```typescript -getAll: () => Promise>; +getAll: () => Promise>; ``` 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 134039cfa91f3f..94b7575519cee0 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md @@ -9,5 +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 a4697ddbbb85ed..c254321e02291f 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md @@ -16,13 +16,13 @@ export interface IUiSettingsClient | Property | Type | Description | | --- | --- | --- | -| [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. | +| [get](./kibana-plugin-server.iuisettingsclient.get.md) | <T = 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 = any>() => Promise<Record<string, T>> | Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. | | [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. | +| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T = 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. | -| [set](./kibana-plugin-server.iuisettingsclient.set.md) | <T extends SavedObjectAttribute = any>(key: string, value: T) => Promise<void> | Writes uiSettings value and marks it as set by the user. | -| [setMany](./kibana-plugin-server.iuisettingsclient.setmany.md) | <T extends SavedObjectAttribute = any>(changes: Record<string, T>) => Promise<void> | Writes multiple uiSettings values and marks them as set by the user. | +| [set](./kibana-plugin-server.iuisettingsclient.set.md) | (key: string, value: any) => Promise<void> | Writes uiSettings value and marks it as set by the user. | +| [setMany](./kibana-plugin-server.iuisettingsclient.setmany.md) | (changes: Record<string, any>) => Promise<void> | Writes multiple uiSettings values and marks them as set by the user. | diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md index bc67d05b3f0ee5..5d5897a7159ad7 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md @@ -9,5 +9,5 @@ Writes uiSettings value and marks it as set by the user. Signature: ```typescript -set: (key: string, value: T) => Promise; +set: (key: string, value: any) => Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md index ec2c24951f0ecd..e1d2595d8e1c76 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md @@ -9,5 +9,5 @@ Writes multiple uiSettings values and marks them as set by the user. Signature: ```typescript -setMany: (changes: Record) => Promise; +setMany: (changes: Record) => Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index b2460cd58f7a72..bc805fdc0b86f1 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -9,7 +9,7 @@ Kibana specific abstraction for an incoming request. Signature: ```typescript -export declare class KibanaRequest +export declare class KibanaRequest ``` ## Constructors @@ -26,7 +26,7 @@ export declare class KibanaRequestHeaders | Readonly copy of incoming request headers. | | [params](./kibana-plugin-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | -| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute> | matched route details | +| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | | | [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md index 88954eedf4cfb5..1905070a99068b 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md @@ -9,5 +9,5 @@ matched route details Signature: ```typescript -readonly route: RecursiveReadonly; +readonly route: RecursiveReadonly>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md index b92fe45d19edb8..29836394582007 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md @@ -9,14 +9,14 @@ Request specific route information exposed to a handler. Signature: ```typescript -export interface KibanaRequestRoute +export interface KibanaRequestRoute ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [method](./kibana-plugin-server.kibanarequestroute.method.md) | RouteMethod | 'patch' | 'options' | | -| [options](./kibana-plugin-server.kibanarequestroute.options.md) | Required<RouteConfigOptions> | | +| [method](./kibana-plugin-server.kibanarequestroute.method.md) | Method | | +| [options](./kibana-plugin-server.kibanarequestroute.options.md) | KibanaRequestRouteOptions<Method> | | | [path](./kibana-plugin-server.kibanarequestroute.path.md) | string | | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md index c003b06e128e43..5775d28b1e053b 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md @@ -7,5 +7,5 @@ Signature: ```typescript -method: RouteMethod | 'patch' | 'options'; +method: Method; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md index 98c898449a5b1d..438263f61eb20a 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md @@ -7,5 +7,5 @@ Signature: ```typescript -options: Required; +options: KibanaRequestRouteOptions; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md b/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md new file mode 100644 index 00000000000000..f48711ac11f927 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) + +## KibanaRequestRouteOptions type + +Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. + +Signature: + +```typescript +export declare type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 360675b3490c26..fceabd1237665d 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -22,6 +22,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | +| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | | [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | ## Enumerations @@ -42,6 +43,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthResultParams](./kibana-plugin-server.authresultparams.md) | Result of an incoming request authentication. | | [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | | [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | +| [Capabilities](./kibana-plugin-server.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. | +| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | +| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | | [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | @@ -73,6 +77,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | | [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | | [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. | +| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. | +| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | | [PackageInfo](./kibana-plugin-server.packageinfo.md) | | | [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. | @@ -83,6 +90,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | +| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | +| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | @@ -96,6 +105,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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) | | +| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.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 | @@ -109,12 +119,16 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | | [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | | [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | | [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | | [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | 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. | | [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | +| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | +| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | | [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | +| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [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 | @@ -127,6 +141,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | | [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | +| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | ## Type Aliases @@ -135,6 +150,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-server.authtoolkit.md). | | [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | | [AuthResult](./kibana-plugin-server.authresult.md) | | +| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | | [ConfigPath](./kibana-plugin-server.configpath.md) | | | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | @@ -148,7 +165,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | +| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | +| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | | [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | | [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. | @@ -157,6 +176,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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). | +| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | | [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | | [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | @@ -169,11 +189,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | | [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | | [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | +| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | -| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Handler to declare a route. | +| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md new file mode 100644 index 00000000000000..8736020daf0638 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) > [headers](./kibana-plugin-server.onpreresponseextensions.headers.md) + +## OnPreResponseExtensions.headers property + +additional headers to attach to the response + +Signature: + +```typescript +headers?: ResponseHeaders; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md new file mode 100644 index 00000000000000..e5aa624c39909a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) + +## OnPreResponseExtensions interface + +Additional data to extend a response. + +Signature: + +```typescript +export interface OnPreResponseExtensions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [headers](./kibana-plugin-server.onpreresponseextensions.headers.md) | ResponseHeaders | additional headers to attach to the response | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md new file mode 100644 index 00000000000000..082de0a9b4aebf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) + +## OnPreResponseHandler type + +See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). + +Signature: + +```typescript +export declare type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md new file mode 100644 index 00000000000000..736b4298037cf3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) + +## OnPreResponseInfo interface + +Response status code. + +Signature: + +```typescript +export interface OnPreResponseInfo +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md new file mode 100644 index 00000000000000..4fd4529dc400fe --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) > [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md) + +## OnPreResponseInfo.statusCode property + +Signature: + +```typescript +statusCode: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md new file mode 100644 index 00000000000000..5525f5bf60284c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) + +## OnPreResponseToolkit interface + +A tool set defining an outcome of OnPreAuth interceptor for incoming request. + +Signature: + +```typescript +export interface OnPreResponseToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult | To pass request to the next handler | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md new file mode 100644 index 00000000000000..bfb5827b16b2fb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) > [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) + +## OnPreResponseToolkit.next property + +To pass request to the next handler + +Signature: + +```typescript +next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +``` diff --git a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md index 33a26eef8a7cb5..56d064dcb290e4 100644 --- a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md +++ b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md @@ -8,6 +8,9 @@ ```typescript config: { + legacy: { + globalConfig$: Observable; + }; create: () => Observable; createIfExists: () => Observable; }; diff --git a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md index e7aa32edaa293b..c2fadfb779fc97 100644 --- a/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md +++ b/docs/development/core/server/kibana-plugin-server.plugininitializercontext.md @@ -16,7 +16,7 @@ export interface PluginInitializerContext | Property | Type | Description | | --- | --- | --- | -| [config](./kibana-plugin-server.plugininitializercontext.config.md) | {
create: <T = ConfigSchema>() => Observable<T>;
createIfExists: <T = ConfigSchema>() => Observable<T | undefined>;
} | | +| [config](./kibana-plugin-server.plugininitializercontext.config.md) | {
legacy: {
globalConfig$: Observable<SharedGlobalConfig>;
};
create: <T = ConfigSchema>() => Observable<T>;
createIfExists: <T = ConfigSchema>() => Observable<T | undefined>;
} | | | [env](./kibana-plugin-server.plugininitializercontext.env.md) | {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
} | | | [logger](./kibana-plugin-server.plugininitializercontext.logger.md) | LoggerFactory | | | [opaqueId](./kibana-plugin-server.plugininitializercontext.opaqueid.md) | PluginOpaqueId | | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandler.md b/docs/development/core/server/kibana-plugin-server.requesthandler.md index 035d16c9fca3c0..79abfd4293e9fe 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandler.md @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han Signature: ```typescript -export declare type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export declare type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.md b/docs/development/core/server/kibana-plugin-server.routeconfig.md index 769d0dda426447..1970b23c7ec099 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.md @@ -9,14 +9,14 @@ Route specific configuration. Signature: ```typescript -export interface RouteConfig

+export interface RouteConfig

| Type, Method extends RouteMethod> ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | +| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions<Method> | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | | [path](./kibana-plugin-server.routeconfig.path.md) | string | The endpoint \_within\_ the router path to register the route. | | [validate](./kibana-plugin-server.routeconfig.validate.md) | RouteSchemas<P, Q, B> | false | A schema created with @kbn/config-schema that every request will be validated against. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md index 12ca36da6de7cb..90ad294457101f 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md @@ -9,5 +9,5 @@ Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfig Signature: ```typescript -options?: RouteConfigOptions; +options?: RouteConfigOptions; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md new file mode 100644 index 00000000000000..fee5528ce3378e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) > [body](./kibana-plugin-server.routeconfigoptions.body.md) + +## RouteConfigOptions.body property + +Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md). + +Signature: + +```typescript +body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md index b4d210ac0b7110..99339db81065c1 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md @@ -9,7 +9,7 @@ Additional route options. Signature: ```typescript -export interface RouteConfigOptions +export interface RouteConfigOptions ``` ## Properties @@ -17,5 +17,6 @@ export interface RouteConfigOptions | Property | Type | Description | | --- | --- | --- | | [authRequired](./kibana-plugin-server.routeconfigoptions.authrequired.md) | boolean | A flag shows that authentication for a route: enabled when true disabled when falseEnabled by default. | +| [body](./kibana-plugin-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody | Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md). | | [tags](./kibana-plugin-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md new file mode 100644 index 00000000000000..f48c9a1d73b11e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) + +## RouteConfigOptionsBody.accepts property + +A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response. + +Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* + +Signature: + +```typescript +accepts?: RouteContentType | RouteContentType[] | string | string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md new file mode 100644 index 00000000000000..3d22dc07d5bae0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) + +## RouteConfigOptionsBody.maxBytes property + +Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory. + +Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`. + +Signature: + +```typescript +maxBytes?: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md new file mode 100644 index 00000000000000..6ef04de459fcf2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) + +## RouteConfigOptionsBody interface + +Additional body options for a route + +Signature: + +```typescript +export interface RouteConfigOptionsBody +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) | RouteContentType | RouteContentType[] | string | string[] | A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response.Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* | +| [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) | number | Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory.Default value: The one set in the kibana.yml config file under the parameter server.maxPayloadBytes. | +| [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) | typeof validBodyOutput[number] | The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez).Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. | +| [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) | boolean | 'gunzip' | Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded.Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. | + diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md new file mode 100644 index 00000000000000..b84bc709df3eca --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) + +## RouteConfigOptionsBody.output property + +The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez). + +Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. + +Signature: + +```typescript +output?: typeof validBodyOutput[number]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md new file mode 100644 index 00000000000000..d395f67c696690 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) + +## RouteConfigOptionsBody.parse property + +Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded. + +Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. + +Signature: + +```typescript +parse?: boolean | 'gunzip'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routecontenttype.md b/docs/development/core/server/kibana-plugin-server.routecontenttype.md new file mode 100644 index 00000000000000..010388c7b8f177 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routecontenttype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteContentType](./kibana-plugin-server.routecontenttype.md) + +## RouteContentType type + +The set of supported parseable Content-Types + +Signature: + +```typescript +export declare type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routemethod.md b/docs/development/core/server/kibana-plugin-server.routemethod.md index dd1a050708bb35..4f83344f842b3c 100644 --- a/docs/development/core/server/kibana-plugin-server.routemethod.md +++ b/docs/development/core/server/kibana-plugin-server.routemethod.md @@ -9,5 +9,5 @@ The set of common HTTP methods supported by Kibana routing. Signature: ```typescript -export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeregistrar.md b/docs/development/core/server/kibana-plugin-server.routeregistrar.md index 535927dc73743f..0f5f49636fdd5e 100644 --- a/docs/development/core/server/kibana-plugin-server.routeregistrar.md +++ b/docs/development/core/server/kibana-plugin-server.routeregistrar.md @@ -4,10 +4,10 @@ ## RouteRegistrar type -Handler to declare a route. +Route handler common definition Signature: ```typescript -export declare type RouteRegistrar =

(route: RouteConfig, handler: RequestHandler) => void; +export declare type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.body.md b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md new file mode 100644 index 00000000000000..78a9d25c25d9d6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [body](./kibana-plugin-server.routeschemas.body.md) + +## RouteSchemas.body property + +Signature: + +```typescript +body?: B; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.md b/docs/development/core/server/kibana-plugin-server.routeschemas.md new file mode 100644 index 00000000000000..77b980551a8ffe --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) + +## RouteSchemas interface + +RouteSchemas contains the schemas for validating the different parts of a request. + +Signature: + +```typescript +export interface RouteSchemas

| Type> +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [body](./kibana-plugin-server.routeschemas.body.md) | B | | +| [params](./kibana-plugin-server.routeschemas.params.md) | P | | +| [query](./kibana-plugin-server.routeschemas.query.md) | Q | | + diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.params.md b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md new file mode 100644 index 00000000000000..3dbf9fed94dc09 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [params](./kibana-plugin-server.routeschemas.params.md) + +## RouteSchemas.params property + +Signature: + +```typescript +params?: P; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.query.md b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md new file mode 100644 index 00000000000000..5be5830cb4bc87 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [query](./kibana-plugin-server.routeschemas.query.md) + +## RouteSchemas.query property + +Signature: + +```typescript +query?: Q; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md deleted file mode 100644 index 0bcca3ec57b546..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [(constructor)](./kibana-plugin-server.savedobjectsclient._constructor_.md) - -## SavedObjectsClient.(constructor) - -Constructs a new instance of the `SavedObjectsClient` class - -Signature: - -```typescript -constructor(repository: SavedObjectsRepository); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| repository | SavedObjectsRepository | | - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md index cc00934a1e1fd5..17d29bb912c834 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md @@ -4,19 +4,12 @@ ## SavedObjectsClient class - Signature: ```typescript export declare class SavedObjectsClient ``` -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(repository)](./kibana-plugin-server.savedobjectsclient._constructor_.md) | | Constructs a new instance of the SavedObjectsClient class | - ## Properties | Property | Modifiers | Type | Description | @@ -37,3 +30,7 @@ export declare class SavedObjectsClient | [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object | | [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject | +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md new file mode 100644 index 00000000000000..9e307597206800 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) + +## SavedObjectsClientFactory type + +Describes the factory used to create instances of the Saved Objects Client. + +Signature: + +```typescript +export declare type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md new file mode 100644 index 00000000000000..df4ce1b4b84286 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) + +## SavedObjectsDeleteByNamespaceOptions interface + + +Signature: + +```typescript +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md new file mode 100644 index 00000000000000..2332520ac388fc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) > [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) + +## SavedObjectsDeleteByNamespaceOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md new file mode 100644 index 00000000000000..38ee40157888f5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) + +## SavedObjectsIncrementCounterOptions interface + + +Signature: + +```typescript +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) | SavedObjectsMigrationVersion | | +| [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md new file mode 100644 index 00000000000000..3b80dea4fecde6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) + +## SavedObjectsIncrementCounterOptions.migrationVersion property + +Signature: + +```typescript +migrationVersion?: SavedObjectsMigrationVersion; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md new file mode 100644 index 00000000000000..acd8d6f0916f9f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) + +## SavedObjectsIncrementCounterOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md new file mode 100644 index 00000000000000..003bc6ac72466d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkCreate](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) + +## SavedObjectsRepository.bulkCreate() method + +Creates multiple documents at once + +Signature: + +```typescript +bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkCreateObject<T>> | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + +{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md new file mode 100644 index 00000000000000..605984d5dea30d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkGet](./kibana-plugin-server.savedobjectsrepository.bulkget.md) + +## SavedObjectsRepository.bulkGet() method + +Returns an array of objects by id + +Signature: + +```typescript +bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | SavedObjectsBulkGetObject[] | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_objects: \[{ id, type, version, attributes }\] } + +## Example + +bulkGet(\[ { id: 'one', type: 'config' }, { id: 'foo', type: 'index-pattern' } \]) + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md new file mode 100644 index 00000000000000..52a73c83b4c3a4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkUpdate](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) + +## SavedObjectsRepository.bulkUpdate() method + +Updates multiple objects in bulk + +Signature: + +```typescript +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkUpdateObject<T>> | | +| options | SavedObjectsBulkUpdateOptions | | + +Returns: + +`Promise>` + +{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md new file mode 100644 index 00000000000000..3a731629156e23 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [create](./kibana-plugin-server.savedobjectsrepository.create.md) + +## SavedObjectsRepository.create() method + +Persists an object + +Signature: + +```typescript +create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| attributes | T | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + +{promise} - { id, type, version, attributes } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md new file mode 100644 index 00000000000000..52c36d2da162d2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [delete](./kibana-plugin-server.savedobjectsrepository.delete.md) + +## SavedObjectsRepository.delete() method + +Deletes an object + +Signature: + +```typescript +delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsDeleteOptions | | + +Returns: + +`Promise<{}>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md new file mode 100644 index 00000000000000..ab6eb30e664f19 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [deleteByNamespace](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) + +## SavedObjectsRepository.deleteByNamespace() method + +Deletes all objects from the provided namespace. + +Signature: + +```typescript +deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| namespace | string | | +| options | SavedObjectsDeleteByNamespaceOptions | | + +Returns: + +`Promise` + +{promise} - { took, timed\_out, total, deleted, batches, version\_conflicts, noops, retries, failures } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md new file mode 100644 index 00000000000000..3c2855ed9a50cb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [find](./kibana-plugin-server.savedobjectsrepository.find.md) + +## SavedObjectsRepository.find() method + +Signature: + +```typescript +find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, } | SavedObjectsFindOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_objects: \[{ id, type, version, attributes }\], total, per\_page, page } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md new file mode 100644 index 00000000000000..dd1d81f225937d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [get](./kibana-plugin-server.savedobjectsrepository.get.md) + +## SavedObjectsRepository.get() method + +Gets a single object + +Signature: + +```typescript +get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { id, type, version, attributes } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md new file mode 100644 index 00000000000000..f20e9a73d99a14 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md @@ -0,0 +1,43 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [incrementCounter](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) + +## SavedObjectsRepository.incrementCounter() method + +Increases a counter field by one. Creates the document if one doesn't exist for the given id. + +Signature: + +```typescript +incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| counterFieldName | string | | +| options | SavedObjectsIncrementCounterOptions | | + +Returns: + +`Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md new file mode 100644 index 00000000000000..681b2233a1e87a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) + +## SavedObjectsRepository class + + +Signature: + +```typescript +export declare class SavedObjectsRepository +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [bulkCreate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) | | Creates multiple documents at once | +| [bulkGet(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkget.md) | | Returns an array of objects by id | +| [bulkUpdate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) | | Updates multiple objects in bulk | +| [create(type, attributes, options)](./kibana-plugin-server.savedobjectsrepository.create.md) | | Persists an object | +| [delete(type, id, options)](./kibana-plugin-server.savedobjectsrepository.delete.md) | | Deletes an object | +| [deleteByNamespace(namespace, options)](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. | +| [find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, })](./kibana-plugin-server.savedobjectsrepository.find.md) | | | +| [get(type, id, options)](./kibana-plugin-server.savedobjectsrepository.get.md) | | Gets a single object | +| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. | +| [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsrepository.update.md) | | Updates an object | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md new file mode 100644 index 00000000000000..15890ab9211aa8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [update](./kibana-plugin-server.savedobjectsrepository.update.md) + +## SavedObjectsRepository.update() method + +Updates an object + +Signature: + +```typescript +update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| attributes | Partial<T> | | +| options | SavedObjectsUpdateOptions | | + +Returns: + +`Promise>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md new file mode 100644 index 00000000000000..e787d737ada17e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) + +## SavedObjectsServiceSetup.addClientWrapper property + +Add a client wrapper with the given priority. + +Signature: + +```typescript +addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md new file mode 100644 index 00000000000000..492aa1a2453a19 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) + +## SavedObjectsServiceSetup.createInternalRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. + +Signature: + +```typescript +createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md new file mode 100644 index 00000000000000..fc5aa40c21a208 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) + +## SavedObjectsServiceSetup.createScopedRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. + +Signature: + +```typescript +createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md new file mode 100644 index 00000000000000..dd97b45f590e2d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -0,0 +1,35 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +## SavedObjectsServiceSetup interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. + +Signature: + +```typescript +export interface SavedObjectsServiceSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | +| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | + +## Remarks + +Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + +When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor. + +## Example + +import {SavedObjectsClient, CoreSetup} from 'src/core/server'; + +export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md new file mode 100644 index 00000000000000..544e0b9d5fa736 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) + +## SavedObjectsServiceSetup.setClientFactory property + +Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. + +Signature: + +```typescript +setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md new file mode 100644 index 00000000000000..e87979a124bdc1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) + +## SavedObjectsServiceStart.getScopedClient property + +Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client. + +A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). + +Signature: + +```typescript +getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md new file mode 100644 index 00000000000000..5a869b3b6c1cbc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +## SavedObjectsServiceStart interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. + +Signature: + +```typescript +export interface SavedObjectsServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). | + diff --git a/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.isvalid.md b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.isvalid.md new file mode 100644 index 00000000000000..6e5f6acca2eb90 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.isvalid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) > [isValid](./kibana-plugin-server.sessioncookievalidationresult.isvalid.md) + +## SessionCookieValidationResult.isValid property + +Whether the cookie is valid or not. + +Signature: + +```typescript +isValid: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.md b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.md new file mode 100644 index 00000000000000..6d32c4cca3dd6a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) + +## SessionCookieValidationResult interface + +Return type from a function to validate cookie contents. + +Signature: + +```typescript +export interface SessionCookieValidationResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [isValid](./kibana-plugin-server.sessioncookievalidationresult.isvalid.md) | boolean | Whether the cookie is valid or not. | +| [path](./kibana-plugin-server.sessioncookievalidationresult.path.md) | string | The "Path" attribute of the cookie; if the cookie is invalid, this is used to clear it. | + diff --git a/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.path.md b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.path.md new file mode 100644 index 00000000000000..8ca6d452213aac --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.path.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) > [path](./kibana-plugin-server.sessioncookievalidationresult.path.md) + +## SessionCookieValidationResult.path property + +The "Path" attribute of the cookie; if the cookie is invalid, this is used to clear it. + +Signature: + +```typescript +path?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md index 167ab03d7567f5..ef65735e7bdbab 100644 --- a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md @@ -4,7 +4,7 @@ ## SessionStorageCookieOptions.encryptionKey property -A key used to encrypt a cookie value. Should be at least 32 characters long. +A key used to encrypt a cookie's value. Should be at least 32 characters long. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md index de412818142f25..778dc27a190d91 100644 --- a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md @@ -16,8 +16,8 @@ export interface SessionStorageCookieOptions | Property | Type | Description | | --- | --- | --- | -| [encryptionKey](./kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md) | string | A key used to encrypt a cookie value. Should be at least 32 characters long. | +| [encryptionKey](./kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md) | string | A key used to encrypt a cookie's value. Should be at least 32 characters long. | | [isSecure](./kibana-plugin-server.sessionstoragecookieoptions.issecure.md) | boolean | Flag indicating whether the cookie should be sent only via a secure connection. | | [name](./kibana-plugin-server.sessionstoragecookieoptions.name.md) | string | Name of the session cookie. | -| [validate](./kibana-plugin-server.sessionstoragecookieoptions.validate.md) | (sessionValue: T) => boolean | Promise<boolean> | Function called to validate a cookie content. | +| [validate](./kibana-plugin-server.sessionstoragecookieoptions.validate.md) | (sessionValue: T | T[]) => SessionCookieValidationResult | Function called to validate a cookie's decrypted value. | diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md index f3cbfc0d84e18e..effa4b6bbc077c 100644 --- a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md @@ -4,10 +4,10 @@ ## SessionStorageCookieOptions.validate property -Function called to validate a cookie content. +Function called to validate a cookie's decrypted value. Signature: ```typescript -validate: (sessionValue: T) => boolean | Promise; +validate: (sessionValue: T | T[]) => SessionCookieValidationResult; ``` diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md index 7b2114404d7f2e..e0f5f7fadd12f4 100644 --- a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md @@ -9,7 +9,7 @@ Describes the values explicitly set by user. Signature: ```typescript -export interface UserProvidedValues +export interface UserProvidedValues ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.validbodyoutput.md b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md new file mode 100644 index 00000000000000..ea866abf887fb8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) + +## validBodyOutput variable + +The set of valid body.output + +Signature: + +```typescript +validBodyOutput: readonly ["data", "stream"] +``` diff --git a/docs/management/field-formatters/url-formatter.asciidoc b/docs/management/field-formatters/url-formatter.asciidoc index 2cbade44312027..41d4f75603dc69 100644 --- a/docs/management/field-formatters/url-formatter.asciidoc +++ b/docs/management/field-formatters/url-formatter.asciidoc @@ -4,6 +4,8 @@ The `Url` field formatter can take on the following types: * The *Image* type can be used to specify an image directory where a specified image is located. * The *Audio* type can be used to specify an audio directory where a specified audio file is located. +For an *Image* type you can specify width and height attributes. These will be used to set the max width / max height of the image, while keeping the aspect ratio. Image will not be upscaled if it's smaller than the provided size parameters. + You can customize either type of URL field formats with templates. A _URL template_ enables you to add specific values to a partial URL. Use the string `{{value}}` to add the contents of the field to a fixed URL. diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index c983fa2cfe4ba8..37fbc6d78c8e42 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -13,11 +13,20 @@ EMS requests are made to the following domains: * vector.maps.elastic.co **Elastic Maps** makes requests directly from the browser to EMS. -To proxy EMS requests through the Kibana server, set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file. + +[float] +=== Connect to Elastic Maps Service from an internal network + +To connect to EMS when your Kibana server and browser are in an internal network: + +. Set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file to proxy EMS requests through the Kibana server. +. Update your firewall rules to whitelist connections from your Kibana server to the EMS domains listed above. + +NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server. [float] -=== Disabling Elastic Maps Service +=== Disable Elastic Maps Service You might experience EMS connection issues if your Kibana server or browser are on a private network or behind a firewall. If this happens, you can disable the EMS connection to avoid unnecessary EMS requests. diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 0827cf5c730095..f3e7273adedeeb 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -9,7 +9,7 @@ https://github.com/elastic/kibana-docker/tree/{branch}[GitHub]. These images are free to use under the Elastic license. They contain open source and free commercial features and access to paid commercial features. -{xpack-ref}/license-management.html[Start a 30-day trial] to try out all of the +{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/deb.asciidoc b/docs/setup/install/deb.asciidoc index 9cd83f44885cbe..62ab661d9a66cf 100644 --- a/docs/setup/install/deb.asciidoc +++ b/docs/setup/install/deb.asciidoc @@ -7,7 +7,7 @@ Kibana on any Debian-based system such as Debian and Ubuntu. This package is free to use under the Elastic license. It contains open source and free commercial features and access to paid commercial features. -{xpack-ref}/license-management.html[Start a 30-day trial] to try out all of the +{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/rpm.asciidoc b/docs/setup/install/rpm.asciidoc index ddff35c61b5bb8..77a16e67cf2a42 100644 --- a/docs/setup/install/rpm.asciidoc +++ b/docs/setup/install/rpm.asciidoc @@ -11,7 +11,7 @@ such as SLES 11 and CentOS 5. Please see <> instead. This package is free to use under the Elastic license. It contains open source and free commercial features and access to paid commercial features. -{xpack-ref}/license-management.html[Start a 30-day trial] to try out all of the +{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/targz.asciidoc b/docs/setup/install/targz.asciidoc index e3104520292ff3..d2143f39fba50e 100644 --- a/docs/setup/install/targz.asciidoc +++ b/docs/setup/install/targz.asciidoc @@ -6,7 +6,7 @@ are the easiest formats to use when trying out Kibana. These packages are free to use under the Elastic license. They contain open source and free commercial features and access to paid commercial features. -{xpack-ref}/license-management.html[Start a 30-day trial] to try out all of the +{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/windows.asciidoc b/docs/setup/install/windows.asciidoc index b9f0224e676999..db55451f01aae3 100644 --- a/docs/setup/install/windows.asciidoc +++ b/docs/setup/install/windows.asciidoc @@ -5,7 +5,7 @@ Kibana can be installed on Windows using the `.zip` package. This package is free to use under the Elastic license. It contains open source and free commercial features and access to paid commercial features. -{xpack-ref}/license-management.html[Start a 30-day trial] to try out all of the +{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index f2c06a3737c7c9..5cda7b2b214f0b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -65,6 +65,8 @@ connects to this Kibana instance. `elasticsearch.requestHeadersWhitelist:`:: *Default: `[ 'authorization' ]`* List of Kibana client-side headers to send to Elasticsearch. To send *no* client-side headers, set this value to [] (an empty list). +Removing the `authorization` header from being whitelisted means that you cannot +use <> in Kibana. `elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or Elasticsearch. This value must be a positive @@ -147,6 +149,36 @@ will default to `true`. `logging.quiet:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output other than error messages. +`logging.rotate:`:: [experimental] Specifies the options for the logging rotate feature. +When not defined, all the sub options defaults would be applied. +The following example shows a valid logging rotate configuration: ++ +-- + logging.rotate: + enabled: true + everyBytes: 10485760 + keepFiles: 10 +-- + +`logging.rotate.enabled:`:: [experimental] *Default: false* Set the value of this setting to `true` to +enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` +that feature would not take any effect. + +`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the +limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and +this option should be at least greater than 1024. + +`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep +on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` +option has to be in the range of 2 to 1024 files. + +`logging.rotate.pollingInterval:`:: [experimental] *Default: 10000* The number of milliseconds for the polling strategy in case +the `logging.rotate.usePolling` is enabled. That option has to be in the range of 5000 to 3600000 milliseconds. + +`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring +the log file. However, there is some systems where it could not be always accurate. In those cases, if needed, +the `polling` method could be used enabling that option. + `logging.silent:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output. @@ -258,6 +290,12 @@ running behind a proxy. Use the `server.rewriteBasePath` setting to tell Kibana if it should remove the basePath from requests it receives, and to prevent a deprecation warning at startup. This setting cannot end in a slash (`/`). +[[server-compression]]`server.compression.enabled:`:: *Default: `true`* Set to `false` to disable HTTP compression for all responses. + +`server.compression.referrerWhitelist:`:: *Default: none* Specifies an array of trusted hostnames, such as the Kibana host, or a reverse +proxy sitting in front of it. This determines whether HTTP compression may be used for responses, based on the request's `Referer` header. +This setting may not be used when `server.compression.enabled` is set to `false`. + [[server-cors]]`server.cors:`:: *Default: `false`* Set to `true` to enable CORS support. This setting is required to configure `server.cors.origin`. `server.cors.origin:`:: *Default: none* Specifies origins. "origin" must be an array. To use this setting, you must set `server.cors` to `true`. To accept all origins, use `server.cors.origin: ["*"]`. diff --git a/docs/user/monitoring/images/monitoring-logstash-node.jpg b/docs/user/monitoring/images/monitoring-logstash-node.jpg deleted file mode 100644 index e9c3d15e4818e0..00000000000000 Binary files a/docs/user/monitoring/images/monitoring-logstash-node.jpg and /dev/null differ diff --git a/docs/user/monitoring/images/monitoring-logstash-node.png b/docs/user/monitoring/images/monitoring-logstash-node.png new file mode 100644 index 00000000000000..750e683b72aa5d Binary files /dev/null and b/docs/user/monitoring/images/monitoring-logstash-node.png differ diff --git a/docs/user/monitoring/images/monitoring-logstash-nodes.jpg b/docs/user/monitoring/images/monitoring-logstash-nodes.jpg deleted file mode 100644 index 62443179e332ff..00000000000000 Binary files a/docs/user/monitoring/images/monitoring-logstash-nodes.jpg and /dev/null differ diff --git a/docs/user/monitoring/images/monitoring-logstash-nodes.png b/docs/user/monitoring/images/monitoring-logstash-nodes.png new file mode 100644 index 00000000000000..559a69ae9b191b Binary files /dev/null and b/docs/user/monitoring/images/monitoring-logstash-nodes.png differ diff --git a/docs/user/monitoring/images/monitoring-logstash-overview.jpg b/docs/user/monitoring/images/monitoring-logstash-overview.jpg deleted file mode 100644 index 07a453d71d2eb3..00000000000000 Binary files a/docs/user/monitoring/images/monitoring-logstash-overview.jpg and /dev/null differ diff --git a/docs/user/monitoring/images/monitoring-logstash-overview.png b/docs/user/monitoring/images/monitoring-logstash-overview.png new file mode 100644 index 00000000000000..7b7ea3baaa02d6 Binary files /dev/null and b/docs/user/monitoring/images/monitoring-logstash-overview.png differ diff --git a/docs/user/monitoring/logstash-details.asciidoc b/docs/user/monitoring/logstash-details.asciidoc index 7f2dac9c47f501..1433a6a036ca8a 100644 --- a/docs/user/monitoring/logstash-details.asciidoc +++ b/docs/user/monitoring/logstash-details.asciidoc @@ -9,16 +9,19 @@ If you are monitoring Logstash nodes, click **Overview** in the Logstash section of the *Stack Monitoring* page in {kib}. You can view the overall health of the Logstash nodes. -image::user/monitoring/images/monitoring-logstash-overview.jpg["Logstash Overview",link="images/monitoring-logstash-overview.jpg"] +[role="screenshot"] +image::user/monitoring/images/monitoring-logstash-overview.png["Logstash Overview",link="images/monitoring-logstash-overview.png"] To view Logstash node metrics, click **Nodes**. The Nodes section shows the status of each Logstash node. -image::user/monitoring/images/monitoring-logstash-nodes.jpg["Logstash Nodes",link="images/monitoring-logstash-nodes.jpg"] +[role="screenshot"] +image::user/monitoring/images/monitoring-logstash-nodes.png["Logstash Nodes",link="images/monitoring-logstash-nodes.png"] Click the name of a node to view its statistics over time. -image::user/monitoring/images/monitoring-logstash-node.jpg["Logstash Node View",link="images/monitoring-logstash-node.jpg"] +[role="screenshot"] +image::user/monitoring/images/monitoring-logstash-node.png["Logstash Node View",link="images/monitoring-logstash-node.png"] For more information, see {logstash-ref}/monitoring-logstash.html[Monitoring Logstash]. diff --git a/docs/user/security/images/role-index-privilege.png b/docs/user/security/images/role-index-privilege.png new file mode 100644 index 00000000000000..1dc1ae640e3ba3 Binary files /dev/null and b/docs/user/security/images/role-index-privilege.png differ diff --git a/docs/user/security/images/role-management.png b/docs/user/security/images/role-management.png new file mode 100644 index 00000000000000..2a78c69a5e3528 Binary files /dev/null and b/docs/user/security/images/role-management.png differ diff --git a/docs/user/security/images/role-new-user.png b/docs/user/security/images/role-new-user.png new file mode 100644 index 00000000000000..0e8d75421cca39 Binary files /dev/null and b/docs/user/security/images/role-new-user.png differ diff --git a/docs/user/security/images/role-space-visualization.png b/docs/user/security/images/role-space-visualization.png new file mode 100644 index 00000000000000..746af89c66e85d Binary files /dev/null and b/docs/user/security/images/role-space-visualization.png differ diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index f57d1bcd3bc2aa..eab3833b3f5ae3 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -37,4 +37,4 @@ cause Kibana's authorization to behave unexpectedly. include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] include::api-keys/index.asciidoc[] - +include::rbac_tutorial.asciidoc[] diff --git a/docs/user/security/rbac_tutorial.asciidoc b/docs/user/security/rbac_tutorial.asciidoc new file mode 100644 index 00000000000000..e4dbdc2483f702 --- /dev/null +++ b/docs/user/security/rbac_tutorial.asciidoc @@ -0,0 +1,104 @@ +[[space-rbac-tutorial]] +=== Tutorial: Use role-based access control to customize Kibana spaces + +With role-based access control (RBAC), you can provide users access to data, tools, +and Kibana spaces. In this tutorial, you will learn how to configure roles +that provide the right users with the right access to the data, tools, and +Kibana spaces. + +[float] +==== Scenario + +Our user is a web developer working on a bank's +online mortgage service. The web developer has these +three requirements: + +* Have access to the data for that service +* Build visualizations and dashboards +* Monitor the performance of the system + +You'll provide the web developer with the access and privileges to get the job done. + +[float] +==== Prerequisites + +To complete this tutorial, you'll need the following: + +* **Administrative privileges**: You must have a role that grants privileges to create a space, role, and user. This is any role which grants the `manage_security` cluster privilege. By default, the `superuser` role provides this access. See the {ref}/built-in-roles.html[built-in] roles. +* **A space**: In this tutorial, use `Dev Mortgage` as the space +name. See <> for +details on creating a space. +* **Data**: You can use <> or +live data. In the steps below, Filebeat and Metricbeat data are used. + +[float] +==== Steps + +With the requirements in mind, here are the steps that you will work +through in this tutorial: + +* Create a role named `mortgage-developer` +* Give the role permission to access the data in the relevant indices +* Give the role permission to create visualizations and dashboards +* Create the web developer's user account with the proper roles + +[float] +==== Create a role + +Go to **Management > Roles** +for an overview of your roles. This view provides actions +for you to create, edit, and delete roles. + +[role="screenshot"] +image::security/images/role-management.png["Role management"] + + +You can create as many roles as you like. Click *Create role* and +provide a name. Use `dev-mortgage` because this role is for a developer +working on the bank's mortgage application. + + +[float] +==== Give the role permission to access the data + +Access to data in indices is an index-level privilege, so in +*Index privileges*, add lines for the indices that contain the +data for this role. Two privileges are required: `read` and +`view_index_metadata`. All privileges are detailed in the +https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html[security privileges] documentation. + +In the screenshots, Filebeat and Metricbeat data is used, but you +should use the index patterns for your indices. + +[role="screenshot"] +image::security/images/role-index-privilege.png["Index privilege"] + +[float] +==== Give the role permission to create visualizations and dashboards + +By default, roles do not give Kibana privileges. Click **Add space +privilege** and associate this role with the `Dev Mortgage` space. + +To enable users with the `dev-mortgage` role to create visualizations +and dashboards, click *All* for *Visualize* and *Dashboard*. Also +assign *All* for *Discover* because it is common for developers +to create saved searches while designing visualizations. + +[role="screenshot"] +image::security/images/role-space-visualization.png["Associate space"] + +[float] +==== Create the developer's user account with the proper roles + +Go to **Management > Users** and click on **Create user** to create a +user. Give the user the `dev-mortgage` role +and the `monitoring-user` role, which is required for users of **Stack Monitoring**. + +[role="screenshot"] +image::security/images/role-new-user.png["Developer user"] + +Finally, have the developer log in and access the Dev Mortgage space +and create a new visualization. + +NOTE: If the user is assigned to only one space, they will automatically enter that space on login. + diff --git a/docs/visualize/most-frequent.asciidoc b/docs/visualize/most-frequent.asciidoc index 7452f1c4c3d7ed..e9085d18185ec3 100644 --- a/docs/visualize/most-frequent.asciidoc +++ b/docs/visualize/most-frequent.asciidoc @@ -7,20 +7,19 @@ levels of {es} {ref}/search-aggregations-bucket.html[bucket] aggregations. The most frequently used visualizations include: -* Line, Area and Bar charts +* Line, area, and bar charts * Pie charts -* Data table -* Metric visualization -* Goal and Gauge visualization +* Data tables +* Metric, goals, and gauges * Heat maps -* Tag cloud +* Tag clouds [float] === Configure your visualization -You configure visualizations using the default editor, which is broken into *Metrics* and *Buckets*, and includes a default count +You configure visualizations using the default editor, which is broken into metrics and buckets, and includes a default count metric. Each visualization supports different configurations for what the metrics and buckets -represent. For example, a Bar chart allows you to add an X-axis: +represent. For example, a bar chart allows you to add an X-axis: [role="screenshot"] image::images/add-bucket.png["",height=478] diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000000..7cade0b35f8209 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +## Example plugins + +This folder contains example plugins. To run the plugins in this folder, use the `--run-examples` flag, via + +``` +yarn start --run-examples +``` + diff --git a/examples/demo_search/README.md b/examples/demo_search/README.md new file mode 100644 index 00000000000000..f0b461e3287b40 --- /dev/null +++ b/examples/demo_search/README.md @@ -0,0 +1,8 @@ +## Demo search strategy + +This example registers a custom search strategy that simply takes a name string in the request and returns the +string `Hello {name}` + +To see the demo search strategy in action, navigate to the `Search explorer` app. + +To run these examples, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/examples/demo_search/common/index.ts b/examples/demo_search/common/index.ts new file mode 100644 index 00000000000000..6587ee96ef61ba --- /dev/null +++ b/examples/demo_search/common/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 { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../src/plugins/data/public'; + +export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY'; + +export interface IDemoRequest extends IKibanaSearchRequest { + mood: string | 'sad' | 'happy'; + name: string; +} + +export interface IDemoResponse extends IKibanaSearchResponse { + greeting: string; +} diff --git a/test/plugin_functional/plugins/demo_search/kibana.json b/examples/demo_search/kibana.json similarity index 100% rename from test/plugin_functional/plugins/demo_search/kibana.json rename to examples/demo_search/kibana.json diff --git a/examples/demo_search/package.json b/examples/demo_search/package.json new file mode 100644 index 00000000000000..404002a50e7103 --- /dev/null +++ b/examples/demo_search/package.json @@ -0,0 +1,17 @@ +{ + "name": "demo_data_search", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/demo_data_search", + "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/examples/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts new file mode 100644 index 00000000000000..d2854151e14c85 --- /dev/null +++ b/examples/demo_search/public/demo_search_strategy.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 { Observable } from 'rxjs'; +import { + ISearchContext, + SYNC_SEARCH_STRATEGY, + ISearchGeneric, +} from '../../../src/plugins/data/public'; +import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public'; + +import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; + +/** + * This demo search strategy provider simply provides a shortcut for calling the DEMO_SEARCH_STRATEGY + * on the server side, without users having to pass it in explicitly, and it takes advantage of the + * already registered SYNC_SEARCH_STRATEGY that exists on the client. + * + * so instead of callers having to do: + * + * ``` + * context.search( + * { ...request, serverStrategy: DEMO_SEARCH_STRATEGY }, + * options, + * SYNC_SEARCH_STRATEGY + * ) as Observable, + *``` + + * They can instead just do + * + * ``` + * context.search(request, options, DEMO_SEARCH_STRATEGY); + * ``` + * + * and are ensured type safety in regard to the request and response objects. + * + * @param context - context supplied by other plugins. + * @param search - a search function to access other strategies that have already been registered. + */ +export const demoClientSearchStrategyProvider: TSearchStrategyProvider = ( + context: ISearchContext, + search: ISearchGeneric +): ISearchStrategy => { + return { + search: (request, options) => + search( + { ...request, serverStrategy: DEMO_SEARCH_STRATEGY }, + options, + SYNC_SEARCH_STRATEGY + ) as Observable, + }; +}; diff --git a/test/plugin_functional/plugins/demo_search/public/index.ts b/examples/demo_search/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/demo_search/public/index.ts rename to examples/demo_search/public/index.ts diff --git a/examples/demo_search/public/plugin.ts b/examples/demo_search/public/plugin.ts new file mode 100644 index 00000000000000..81ba585b991902 --- /dev/null +++ b/examples/demo_search/public/plugin.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 { DataPublicPluginSetup } from '../../../src/plugins/data/public'; +import { Plugin, CoreSetup, PluginInitializerContext } from '../../../src/core/public'; +import { DEMO_SEARCH_STRATEGY } from '../common'; +import { demoClientSearchStrategyProvider } from './demo_search_strategy'; +import { IDemoRequest, IDemoResponse } from '../common'; + +interface DemoDataSearchSetupDependencies { + data: DataPublicPluginSetup; +} + +/** + * Add the typescript mappings for our search strategy to the request and + * response types. This allows typescript to require the right shapes if + * making the call: + * const response = context.search.search(request, {}, DEMO_SEARCH_STRATEGY); + * + * If the caller does not pass in the right `request` shape, typescript will + * complain. The caller will also get a typed response. + */ +declare module '../../../src/plugins/data/public' { + export interface IRequestTypesMap { + [DEMO_SEARCH_STRATEGY]: IDemoRequest; + } + + export interface IResponseTypesMap { + [DEMO_SEARCH_STRATEGY]: IDemoResponse; + } +} + +export class DemoDataPlugin implements Plugin { + constructor(private initializerContext: PluginInitializerContext) {} + public setup(core: CoreSetup, deps: DemoDataSearchSetupDependencies) { + deps.data.search.registerSearchStrategyProvider( + this.initializerContext.opaqueId, + DEMO_SEARCH_STRATEGY, + demoClientSearchStrategyProvider + ); + } + + public start() {} + public stop() {} +} diff --git a/examples/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts new file mode 100644 index 00000000000000..5b0883be1fc514 --- /dev/null +++ b/examples/demo_search/server/demo_search_strategy.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 { TSearchStrategyProvider } from '../../../src/plugins/data/server'; +import { DEMO_SEARCH_STRATEGY } from '../common'; + +export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { + return { + search: request => { + return Promise.resolve({ + greeting: + request.mood === 'happy' + ? `Lovely to meet you, ${request.name}` + : `Hope you feel better, ${request.name}`, + }); + }, + }; +}; diff --git a/test/plugin_functional/plugins/demo_search/server/index.ts b/examples/demo_search/server/index.ts similarity index 100% rename from test/plugin_functional/plugins/demo_search/server/index.ts rename to examples/demo_search/server/index.ts diff --git a/examples/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts new file mode 100644 index 00000000000000..23c82225563c89 --- /dev/null +++ b/examples/demo_search/server/plugin.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 { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; +import { DataPluginSetup } from 'src/plugins/data/server/plugin'; +import { demoSearchStrategyProvider } from './demo_search_strategy'; +import { DEMO_SEARCH_STRATEGY, IDemoRequest, IDemoResponse } from '../common'; + +interface IDemoSearchExplorerDeps { + data: DataPluginSetup; +} + +/** + * Add the typescript mappings for our search strategy to the request and + * response types. This allows typescript to require the right shapes if + * making the call: + * const response = context.search.search(request, DEMO_SEARCH_STRATEGY); + * + * If the caller does not pass in the right `request` shape, typescript will + * complain. The caller will also get a typed response. + */ +declare module '../../../src/plugins/data/server' { + export interface IRequestTypesMap { + [DEMO_SEARCH_STRATEGY]: IDemoRequest; + } + + export interface IResponseTypesMap { + [DEMO_SEARCH_STRATEGY]: IDemoResponse; + } +} + +export class DemoDataPlugin implements Plugin { + constructor(private initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, deps: IDemoSearchExplorerDeps) { + deps.data.search.registerSearchStrategyProvider( + this.initializerContext.opaqueId, + DEMO_SEARCH_STRATEGY, + demoSearchStrategyProvider + ); + } + + public start() {} + public stop() {} +} diff --git a/examples/demo_search/tsconfig.json b/examples/demo_search/tsconfig.json new file mode 100644 index 00000000000000..7fa03739119b43 --- /dev/null +++ b/examples/demo_search/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*" + ], + "exclude": [] +} diff --git a/examples/search_explorer/README.md b/examples/search_explorer/README.md new file mode 100644 index 00000000000000..0e5a48cf22dc1a --- /dev/null +++ b/examples/search_explorer/README.md @@ -0,0 +1,8 @@ +## Search explorer + +This example search explorer app shows how to use different search strategies in order to retrieve data. + +One demo uses the built in elasticsearch search strategy, and runs a search against data in elasticsearch. The +other demo uses the custom demo search strategy, a custom search strategy registerd inside the [demo_search plugin](../demo_search). + +To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/test/plugin_functional/plugins/search_explorer/kibana.json b/examples/search_explorer/kibana.json similarity index 100% rename from test/plugin_functional/plugins/search_explorer/kibana.json rename to examples/search_explorer/kibana.json diff --git a/examples/search_explorer/package.json b/examples/search_explorer/package.json new file mode 100644 index 00000000000000..62d0127c30cc6d --- /dev/null +++ b/examples/search_explorer/package.json @@ -0,0 +1,17 @@ +{ + "name": "search_explorer", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/search_explorer", + "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/search_explorer/public/application.tsx b/examples/search_explorer/public/application.tsx similarity index 97% rename from test/plugin_functional/plugins/search_explorer/public/application.tsx rename to examples/search_explorer/public/application.tsx index 4762209a548c1b..801a3c615ac613 100644 --- a/test/plugin_functional/plugins/search_explorer/public/application.tsx +++ b/examples/search_explorer/public/application.tsx @@ -28,7 +28,7 @@ import { EuiSideNav, } from '@elastic/eui'; -import { AppMountContext, AppMountParameters } from '../../../../../src/core/public'; +import { AppMountContext, AppMountParameters } from '../../../src/core/public'; import { EsSearchTest } from './es_strategy'; import { Page } from './page'; import { DemoStrategy } from './demo_strategy'; diff --git a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx b/examples/search_explorer/public/demo_strategy.tsx similarity index 98% rename from test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx rename to examples/search_explorer/public/demo_strategy.tsx index 8a0dd31e3595f2..7c6c21d2b7aedb 100644 --- a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx +++ b/examples/search_explorer/public/demo_strategy.tsx @@ -25,7 +25,7 @@ import { EuiFlexGroup, EuiFieldText, } from '@elastic/eui'; -import { ISearchGeneric } from '../../../../../src/plugins/data/public'; +import { ISearchGeneric } from '../../../src/plugins/data/public'; import { DoSearch } from './do_search'; import { GuideSection } from './guide_section'; diff --git a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx b/examples/search_explorer/public/do_search.tsx similarity index 97% rename from test/plugin_functional/plugins/search_explorer/public/do_search.tsx rename to examples/search_explorer/public/do_search.tsx index e039e4ff3f63f1..f279b9fcd6e239 100644 --- a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx +++ b/examples/search_explorer/public/do_search.tsx @@ -21,10 +21,7 @@ import React from 'react'; import { EuiButton, EuiCodeBlock, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; import { EuiProgress } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { - IKibanaSearchResponse, - IKibanaSearchRequest, -} from '../../../../../src/plugins/data/public'; +import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../../src/plugins/data/public'; interface Props { request: IKibanaSearchRequest; diff --git a/test/plugin_functional/plugins/search_explorer/public/documentation.tsx b/examples/search_explorer/public/documentation.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/documentation.tsx rename to examples/search_explorer/public/documentation.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx b/examples/search_explorer/public/es_strategy.tsx similarity index 87% rename from test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx rename to examples/search_explorer/public/es_strategy.tsx index d35c67191a1f80..e26c11a646669d 100644 --- a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx +++ b/examples/search_explorer/public/es_strategy.tsx @@ -29,19 +29,19 @@ import { ISearchGeneric, IEsSearchResponse, IEsSearchRequest, -} from '../../../../../src/plugins/data/public'; +} from '../../../src/plugins/data/public'; import { DoSearch } from './do_search'; import { GuideSection } from './guide_section'; // @ts-ignore -import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_service'; +import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_service'; // @ts-ignore -import serverStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_strategy'; +import serverStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_strategy'; // @ts-ignore -import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_service'; +import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_service'; // @ts-ignore -import publicStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_strategy'; +import publicStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_strategy'; interface Props { search: ISearchGeneric; diff --git a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx b/examples/search_explorer/public/guide_section.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/guide_section.tsx rename to examples/search_explorer/public/guide_section.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/index.ts b/examples/search_explorer/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/index.ts rename to examples/search_explorer/public/index.ts diff --git a/test/plugin_functional/plugins/search_explorer/public/page.tsx b/examples/search_explorer/public/page.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/page.tsx rename to examples/search_explorer/public/page.tsx diff --git a/examples/search_explorer/public/plugin.tsx b/examples/search_explorer/public/plugin.tsx new file mode 100644 index 00000000000000..a7a6fd11341a4b --- /dev/null +++ b/examples/search_explorer/public/plugin.tsx @@ -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 { Plugin, CoreSetup } from 'kibana/public'; +import { ISearchAppMountContext } from '../../../src/plugins/data/public'; + +declare module 'kibana/public' { + interface AppMountContext { + search?: ISearchAppMountContext; + } +} +export class SearchExplorerPlugin implements Plugin { + public setup(core: CoreSetup) { + core.application.register({ + id: 'searchExplorer', + title: 'Search Explorer', + async mount(context, params) { + const { renderApp } = await import('./application'); + return renderApp(context, params); + }, + }); + } + + public start() {} + public stop() {} +} diff --git a/examples/search_explorer/public/search_api.tsx b/examples/search_explorer/public/search_api.tsx new file mode 100644 index 00000000000000..fc68571e4ef68a --- /dev/null +++ b/examples/search_explorer/public/search_api.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 from 'react'; +import { GuideSection } from './guide_section'; + +// @ts-ignore +import publicSetupContract from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_setup'; +// @ts-ignore +import publicSearchStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_strategy'; +// @ts-ignore +import publicSearch from '!!raw-loader!./../../../src/plugins/data/public/search/i_search'; +// @ts-ignore +import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/search_service'; + +// @ts-ignore +import serverSetupContract from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_setup'; +// @ts-ignore +import serverSearchStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_strategy'; +// @ts-ignore +import serverSearch from '!!raw-loader!./../../../src/plugins/data/server/search/i_search'; +// @ts-ignore +import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/search_service'; + +export const SearchApiPage = () => ( + +); diff --git a/examples/search_explorer/tsconfig.json b/examples/search_explorer/tsconfig.json new file mode 100644 index 00000000000000..d508076b331990 --- /dev/null +++ b/examples/search_explorer/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/package.json b/package.json index 9ef96908b9ba75..f0d2743174c7f8 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "typespec": "typings-tester --config x-pack/legacy/plugins/canvas/public/lib/aeroelastic/tsconfig.json x-pack/legacy/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts", "checkLicenses": "node scripts/check_licenses --dev", "build": "node scripts/build --all-platforms", - "start": "node --trace-warnings --trace-deprecation scripts/kibana --dev ", + "start": "node --trace-warnings --throw-deprecation scripts/kibana --dev", "debug": "node --nolazy --inspect scripts/kibana --dev", "debug-break": "node --nolazy --inspect-brk scripts/kibana --dev", "karma": "karma start", @@ -79,23 +79,24 @@ }, "resolutions": { "**/@types/node": "10.12.27", - "**/@types/react": "16.8.3", + "**/@types/react": "^16.9.13", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9", + "**/react-dom": "^16.12.0", + "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2", - "**/react": "16.8.6", - "**/react-dom": "16.8.6", - "**/react-test-renderer": "16.8.6" + "**/serialize-javascript": "^2.1.1" }, "workspaces": { "packages": [ "packages/*", "x-pack", "x-pack/legacy/plugins/*", + "examples/*", "test/plugin_functional/plugins/*", "test/interpreter_functional/plugins/*" ], @@ -113,7 +114,7 @@ "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.0.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -153,6 +154,7 @@ "cache-loader": "^4.1.0", "chalk": "^2.4.2", "check-disk-space": "^2.1.0", + "chokidar": "3.2.1", "color": "1.0.3", "commander": "3.0.0", "compare-versions": "3.5.1", @@ -163,11 +165,13 @@ "d3-cloud": "1.2.5", "deepmerge": "^4.2.2", "del": "^5.1.0", + "elastic-apm-node": "^3.2.0", "elasticsearch": "^16.5.0", "elasticsearch-browser": "^16.5.0", "encode-uri-query": "1.0.1", "execa": "^3.2.0", "expiry-js": "0.1.7", + "fast-deep-equal": "^3.1.1", "file-loader": "4.2.0", "font-awesome": "4.7.0", "getos": "^3.1.0", @@ -202,11 +206,12 @@ "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "lodash.clonedeep": "^4.5.0", "lru-cache": "4.1.5", - "markdown-it": "^8.4.1", + "markdown-it": "^10.0.0", "mini-css-extract-plugin": "0.8.0", "minimatch": "^3.0.4", "moment": "^2.24.0", "moment-timezone": "^0.5.27", + "monaco-editor": "~0.17.0", "mustache": "2.3.2", "ngreact": "0.5.1", "node-fetch": "1.7.3", @@ -219,17 +224,18 @@ "pug": "^2.0.3", "querystring-browser": "1.0.4", "raw-loader": "3.1.0", - "react": "^16.8.6", - "react-addons-shallow-compare": "15.6.2", + "react": "^16.12.0", "react-color": "^2.13.8", - "react-dom": "^16.8.6", + "react-dom": "^16.12.0", "react-grid-layout": "^0.16.2", - "react-hooks-testing-library": "^0.5.0", "react-input-range": "^1.3.0", "react-markdown": "^3.4.1", - "react-redux": "^5.1.1", + "react-monaco-editor": "~0.27.0", + "react-redux": "^5.1.2", + "react-resize-detector": "^4.2.0", "react-router-dom": "^4.3.1", "react-sizeme": "^2.3.6", + "react-use": "^13.10.2", "reactcss": "1.2.3", "redux": "4.0.0", "redux-actions": "2.2.1", @@ -290,6 +296,8 @@ "@microsoft/api-documenter": "7.4.3", "@microsoft/api-extractor": "7.4.2", "@percy/agent": "^0.11.0", + "@testing-library/react": "^9.3.2", + "@testing-library/react-hooks": "^3.2.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", "@types/babel__core": "^7.1.2", @@ -315,7 +323,7 @@ "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", - "@types/jest": "^24.0.18", + "@types/jest": "24.0.19", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", "@types/js-yaml": "^3.11.1", @@ -335,9 +343,10 @@ "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", "@types/reach__router": "^1.2.6", - "@types/react": "^16.8.0", - "@types/react-dom": "^16.8.0", + "@types/react": "^16.9.11", + "@types/react-dom": "^16.9.4", "@types/react-redux": "^6.0.6", + "@types/react-resize-detector": "^4.0.1", "@types/react-router-dom": "^4.3.1", "@types/react-virtualized": "^9.18.7", "@types/redux": "^3.6.31", @@ -350,12 +359,14 @@ "@types/styled-components": "^4.4.0", "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", + "@types/testing-library__react": "^9.1.2", + "@types/testing-library__react-hooks": "^3.1.0", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -367,7 +378,6 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chokidar": "3.2.1", "chromedriver": "78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", @@ -407,13 +417,11 @@ "gulp-sourcemaps": "2.6.5", "has-ansi": "^3.0.0", "iedriver": "^3.14.1", - "image-diff": "1.6.3", "intl-messageformat-parser": "^1.4.0", "is-path-inside": "^2.1.0", "istanbul-instrumenter-loader": "3.0.1", "jest": "^24.9.0", "jest-cli": "^24.9.0", - "jest-dom": "^3.5.0", "jest-raw-loader": "^1.0.1", "jimp": "0.8.4", "json5": "^1.0.1", @@ -437,7 +445,7 @@ "node-sass": "^4.9.4", "normalize-path": "^3.0.0", "nyc": "^14.1.1", - "pixelmatch": "4.0.2", + "pixelmatch": "^5.1.0", "pkg-up": "^2.0.0", "pngjs": "^3.4.0", "postcss": "^7.0.5", diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 98fa62021b5bbe..a7bce739ba8e3f 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -28,6 +28,11 @@ module.exports = { to: false, disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` }, + { + from: '@kbn/elastic-idx', + to: false, + disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` + }, { from: 'x-pack', toRelative: 'x-pack', diff --git a/packages/eslint-config-kibana/javascript.js b/packages/eslint-config-kibana/javascript.js index 0c79669c15e733..7afd3da3a7b939 100644 --- a/packages/eslint-config-kibana/javascript.js +++ b/packages/eslint-config-kibana/javascript.js @@ -40,7 +40,7 @@ module.exports = { rules: { 'block-scoped-var': 'error', - camelcase: [ 'error', { properties: 'never' } ], + camelcase: [ 'error', { properties: 'never', allow: ['^UNSAFE_'] } ], 'comma-dangle': 'off', 'comma-spacing': ['error', { before: false, after: true }], 'comma-style': [ 'error', 'last' ], diff --git a/packages/eslint-config-kibana/jest.js b/packages/eslint-config-kibana/jest.js index 2aa9627404a6c2..d682277ff905a4 100644 --- a/packages/eslint-config-kibana/jest.js +++ b/packages/eslint-config-kibana/jest.js @@ -16,7 +16,7 @@ module.exports = { rules: { 'jest/no-focused-tests': 'error', 'jest/no-identical-title': 'error', - 'import/order': 'off' + 'import/order': 'off', }, } ] diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index ee65a1cf79148d..7917297883b033 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index 8ffae5edc88ebe..ca3b9bf99c5352 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -94,7 +94,7 @@ module.exports = { '@typescript-eslint/camelcase': ['error', { 'properties': 'never', 'ignoreDestructuring': true, - 'allow': ['^[A-Z0-9_]+$'] + 'allow': ['^[A-Z0-9_]+$', '^UNSAFE_'] }], '@typescript-eslint/class-name-casing': 'error', '@typescript-eslint/explicit-member-accessibility': ['error', @@ -116,6 +116,7 @@ module.exports = { }], '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/triple-slash-reference': ['error', { @@ -167,6 +168,7 @@ module.exports = { 'object-shorthand': 'error', 'one-var': [ 'error', 'never' ], 'prefer-const': 'error', + 'prefer-rest-params': 'error', 'quotes': ['error', 'double', { 'avoidEscape': true }], 'quote-props': ['error', 'consistent-as-needed'], 'radix': 'error', diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index 3736ab15260fa1..b7fbe629246eca 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -1,95 +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. - */ - -const { resolve } = require('path'); - -const del = require('del'); -const supportsColor = require('supports-color'); -const { run, withProcRunner } = require('@kbn/dev-utils'); - -const ROOT_DIR = resolve(__dirname, '..'); -const BUILD_DIR = resolve(ROOT_DIR, 'target'); - -const padRight = (width, str) => - str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; - -run( - async ({ log, flags }) => { - await withProcRunner(log, async proc => { - log.info('Deleting old output'); - await del(BUILD_DIR); - - const cwd = ROOT_DIR; - const env = { ...process.env }; - if (supportsColor.stdout) { - env.FORCE_COLOR = 'true'; - } - - log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); - await Promise.all([ - ...['web', 'node'].map(subTask => - proc.run(padRight(10, `babel:${subTask}`), { - cmd: 'babel', - args: [ - 'src', - '--config-file', - require.resolve('../babel.config.js'), - '--out-dir', - resolve(BUILD_DIR, subTask), - '--extensions', - '.ts,.js,.tsx', - ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), - ], - wait: true, - env: { - ...env, - BABEL_ENV: subTask, - }, - cwd, - }) - ), - - proc.run(padRight(10, 'tsc'), { - cmd: 'tsc', - args: [ - '--emitDeclarationOnly', - ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), - ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), - ], - wait: true, - env, - cwd, - }), - ]); - - log.success('Complete'); - }); - }, - { - description: 'Simple build tool for @kbn/analytics package', - flags: { - boolean: ['watch', 'source-maps'], - help: ` - --watch Run in watch mode - --source-maps Include sourcemaps - `, - }, - } -); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { resolve } = require('path'); + +const del = require('del'); +const supportsColor = require('supports-color'); +const { run, withProcRunner } = require('@kbn/dev-utils'); + +const ROOT_DIR = resolve(__dirname, '..'); +const BUILD_DIR = resolve(ROOT_DIR, 'target'); + +const padRight = (width, str) => + str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; + +run( + async ({ log, flags }) => { + await withProcRunner(log, async proc => { + log.info('Deleting old output'); + await del(BUILD_DIR); + + const cwd = ROOT_DIR; + const env = { ...process.env }; + if (supportsColor.stdout) { + env.FORCE_COLOR = 'true'; + } + + log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); + await Promise.all([ + ...['web', 'node'].map(subTask => + proc.run(padRight(10, `babel:${subTask}`), { + cmd: 'babel', + args: [ + 'src', + '--config-file', + require.resolve('../babel.config.js'), + '--out-dir', + resolve(BUILD_DIR, subTask), + '--extensions', + '.ts,.js,.tsx', + ...(flags.watch ? ['--watch'] : ['--quiet']), + ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ], + wait: true, + env: { + ...env, + BABEL_ENV: subTask, + }, + cwd, + }) + ), + + proc.run(padRight(10, 'tsc'), { + cmd: 'tsc', + args: [ + '--emitDeclarationOnly', + ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), + ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), + ], + wait: true, + env, + cwd, + }), + ]); + + log.success('Complete'); + }); + }, + { + description: 'Simple build tool for @kbn/analytics package', + flags: { + boolean: ['watch', 'source-maps'], + help: ` + --watch Run in watch mode + --source-maps Include sourcemaps + `, + }, + } +); diff --git a/packages/kbn-babel-code-parser/src/strategies.js b/packages/kbn-babel-code-parser/src/strategies.js index 89621bc53bd534..f116abde9e0e6d 100644 --- a/packages/kbn-babel-code-parser/src/strategies.js +++ b/packages/kbn-babel-code-parser/src/strategies.js @@ -20,6 +20,7 @@ import { canRequire } from './can_require'; import { dependenciesVisitorsGenerator } from './visitors'; import { dirname, isAbsolute, resolve } from 'path'; +import { builtinModules } from 'module'; export function _calculateTopLevelDependency(inputDep, outputDep = '') { // The path separator will be always the forward slash @@ -48,14 +49,18 @@ export function _calculateTopLevelDependency(inputDep, outputDep = '') { return _calculateTopLevelDependency(depSplitPaths.join(pathSeparator), outputDep); } -export async function dependenciesParseStrategy(cwd, parseSingleFile, mainEntry, wasParsed, results) { - // Retrieve native nodeJS modules - const natives = process.binding('natives'); - +export async function dependenciesParseStrategy( + cwd, + parseSingleFile, + mainEntry, + wasParsed, + results +) { // Get dependencies from a single file and filter // out node native modules from the result - const dependencies = (await parseSingleFile(mainEntry, dependenciesVisitorsGenerator)) - .filter(dep => !natives[dep]); + const dependencies = (await parseSingleFile(mainEntry, dependenciesVisitorsGenerator)).filter( + dep => !builtinModules.includes(dep) + ); // Return the list of all the new entries found into // the current mainEntry that we could use to look for diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index d1b7bc20dd9f94..0de9318ea0c274 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -35,13 +35,6 @@ const plugins = [ // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), ]; -const isTestEnv = process.env.BABEL_ENV === 'test' || process.env.NODE_ENV === 'test'; - -// Only load the idx plugin in non-test environments, since it conflicts with -// Jest's coverage mapping. -if (!isTestEnv) { - plugins.push(require.resolve('@kbn/elastic-idx/babel')); -} module.exports = { presets: [require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')], diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index 1913301e21a764..e554859928c0b7 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -12,7 +12,6 @@ "@babel/preset-env": "^7.5.5", "@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", diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index 8ba2c43b5e1fe6..fd62f1b3c03b22 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -12,6 +12,8 @@ Kibana configuration entries providing developers with a fully typed model of th - [`schema.number()`](#schemanumber) - [`schema.boolean()`](#schemaboolean) - [`schema.literal()`](#schemaliteral) + - [`schema.buffer()`](#schemabuffer) + - [`schema.stream()`](#schemastream) - [Composite types](#composite-types) - [`schema.arrayOf()`](#schemaarrayof) - [`schema.object()`](#schemaobject) @@ -173,6 +175,36 @@ const valueSchema = [ ]; ``` +#### `schema.buffer()` + +Validates input data as a NodeJS `Buffer`. + +__Output type:__ `Buffer` + +__Options:__ + * `defaultValue: TBuffer | Reference | (() => TBuffer)` - defines a default value, see [Default values](#default-values) section for more details. + * `validate: (value: TBuffer) => Buffer | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. + +__Usage:__ +```typescript +const valueSchema = schema.buffer({ defaultValue: Buffer.from('Hi, there!') }); +``` + +#### `schema.stream()` + +Validates input data as a NodeJS `stream`. + +__Output type:__ `Stream`, `Readable` or `Writtable` + +__Options:__ + * `defaultValue: TStream | Reference | (() => TStream)` - defines a default value, see [Default values](#default-values) section for more details. + * `validate: (value: TStream) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. + +__Usage:__ +```typescript +const valueSchema = schema.stream({ defaultValue: new Stream() }); +``` + ### Composite types #### `schema.arrayOf()` diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 210b044421e7e7..56b3096433c247 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -18,6 +18,7 @@ */ import { Duration } from 'moment'; +import { Stream } from 'stream'; import { ByteSizeValue } from './byte_size_value'; import { ContextReference, Reference, SiblingReference } from './references'; @@ -26,6 +27,7 @@ import { ArrayOptions, ArrayType, BooleanType, + BufferType, ByteSizeOptions, ByteSizeType, ConditionalType, @@ -52,6 +54,7 @@ import { UnionType, URIOptions, URIType, + StreamType, } from './types'; export { ObjectType, TypeOf, Type }; @@ -65,6 +68,14 @@ function boolean(options?: TypeOptions): Type { return new BooleanType(options); } +function buffer(options?: TypeOptions): Type { + return new BufferType(options); +} + +function stream(options?: TypeOptions): Type { + return new StreamType(options); +} + function string(options?: StringOptions): Type { return new StringType(options); } @@ -188,6 +199,7 @@ export const schema = { any, arrayOf, boolean, + buffer, byteSize, conditional, contextRef, @@ -201,6 +213,7 @@ export const schema = { object, oneOf, recordOf, + stream, siblingRef, string, uri, diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index e5a5b446de4f5c..4d5091eaa09b13 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -29,6 +29,7 @@ import { } from 'joi'; import { isPlainObject } from 'lodash'; import { isDuration } from 'moment'; +import { Stream } from 'stream'; import { ByteSizeValue, ensureByteSizeValue } from '../byte_size_value'; import { ensureDuration } from '../duration'; @@ -89,6 +90,33 @@ export const internals = Joi.extend([ }, rules: [anyCustomRule], }, + { + name: 'binary', + + base: Joi.binary(), + coerce(value: any, state: State, options: ValidationOptions) { + // If value isn't defined, let Joi handle default value if it's defined. + if (value !== undefined && !(typeof value === 'object' && Buffer.isBuffer(value))) { + return this.createError('binary.base', { value }, state, options); + } + + return value; + }, + rules: [anyCustomRule], + }, + { + name: 'stream', + + pre(value: any, state: State, options: ValidationOptions) { + // If value isn't defined, let Joi handle default value if it's defined. + if (value instanceof Stream) { + return value as any; + } + + return this.createError('stream.base', { value }, state, options); + }, + rules: [anyCustomRule], + }, { name: 'string', diff --git a/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap new file mode 100644 index 00000000000000..96a7ab34dac26a --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Buffer] but got [undefined]"`; + +exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`; + +exports[`returns error when not a buffer 1`] = `"expected value of type [Buffer] but got [number]"`; + +exports[`returns error when not a buffer 2`] = `"expected value of type [Buffer] but got [Array]"`; + +exports[`returns error when not a buffer 3`] = `"expected value of type [Buffer] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap new file mode 100644 index 00000000000000..e813b4f68a09e1 --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Stream] but got [undefined]"`; + +exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`; + +exports[`returns error when not a stream 1`] = `"expected value of type [Stream] but got [number]"`; + +exports[`returns error when not a stream 2`] = `"expected value of type [Stream] but got [Array]"`; + +exports[`returns error when not a stream 3`] = `"expected value of type [Stream] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/buffer_type.test.ts b/packages/kbn-config-schema/src/types/buffer_type.test.ts new file mode 100644 index 00000000000000..63d59296aec843 --- /dev/null +++ b/packages/kbn-config-schema/src/types/buffer_type.test.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 { schema } from '..'; + +test('returns value by default', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer().validate(value)).toStrictEqual(value); +}); + +test('is required by default', () => { + expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot(); +}); + +test('includes namespace in failure', () => { + expect(() => + schema.buffer().validate(undefined, {}, 'foo-namespace') + ).toThrowErrorMatchingSnapshot(); +}); + +describe('#defaultValue', () => { + test('returns default when undefined', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer({ defaultValue: value }).validate(undefined)).toStrictEqual(value); + }); + + test('returns value when specified', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer({ defaultValue: Buffer.from('Bye!') }).validate(value)).toStrictEqual( + value + ); + }); +}); + +test('returns error when not a buffer', () => { + expect(() => schema.buffer().validate(123)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.buffer().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); + + expect(() => schema.buffer().validate('abc')).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/buffer_type.ts b/packages/kbn-config-schema/src/types/buffer_type.ts new file mode 100644 index 00000000000000..194163e5096f04 --- /dev/null +++ b/packages/kbn-config-schema/src/types/buffer_type.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 typeDetect from 'type-detect'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export class BufferType extends Type { + constructor(options?: TypeOptions) { + super(internals.binary(), options); + } + + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'binary.base') { + return `expected value of type [Buffer] but got [${typeDetect(value)}]`; + } + } +} diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index cfa8cc4b7553d5..9db79b8bf9e00b 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -21,6 +21,7 @@ export { Type, TypeOptions } from './type'; export { AnyType } from './any_type'; export { ArrayOptions, ArrayType } from './array_type'; export { BooleanType } from './boolean_type'; +export { BufferType } from './buffer_type'; export { ByteSizeOptions, ByteSizeType } from './byte_size_type'; export { ConditionalType, ConditionalTypeValue } from './conditional_type'; export { DurationOptions, DurationType } from './duration_type'; @@ -30,6 +31,7 @@ export { MapOfOptions, MapOfType } from './map_type'; export { NumberOptions, NumberType } from './number_type'; export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type'; export { RecordOfOptions, RecordOfType } from './record_type'; +export { StreamType } from './stream_type'; export { StringOptions, StringType } from './string_type'; export { UnionType } from './union_type'; export { URIOptions, URIType } from './uri_type'; diff --git a/packages/kbn-config-schema/src/types/stream_type.test.ts b/packages/kbn-config-schema/src/types/stream_type.test.ts new file mode 100644 index 00000000000000..011fa6373df335 --- /dev/null +++ b/packages/kbn-config-schema/src/types/stream_type.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 { schema } from '..'; +import { Stream, Readable, Writable, PassThrough } from 'stream'; + +test('returns value by default', () => { + const value = new Stream(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Readable is valid', () => { + const value = new Readable(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Writable is valid', () => { + const value = new Writable(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Passthrough is valid', () => { + const value = new PassThrough(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('is required by default', () => { + expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot(); +}); + +test('includes namespace in failure', () => { + expect(() => + schema.stream().validate(undefined, {}, 'foo-namespace') + ).toThrowErrorMatchingSnapshot(); +}); + +describe('#defaultValue', () => { + test('returns default when undefined', () => { + const value = new Stream(); + expect(schema.stream({ defaultValue: value }).validate(undefined)).toStrictEqual(value); + }); + + test('returns value when specified', () => { + const value = new Stream(); + expect(schema.stream({ defaultValue: new PassThrough() }).validate(value)).toStrictEqual(value); + }); +}); + +test('returns error when not a stream', () => { + expect(() => schema.stream().validate(123)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.stream().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); + + expect(() => schema.stream().validate('abc')).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/stream_type.ts b/packages/kbn-config-schema/src/types/stream_type.ts new file mode 100644 index 00000000000000..db1559f537490e --- /dev/null +++ b/packages/kbn-config-schema/src/types/stream_type.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 typeDetect from 'type-detect'; +import { Stream } from 'stream'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export class StreamType extends Type { + constructor(options?: TypeOptions) { + super(internals.stream(), options); + } + + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'stream.base') { + return `expected value of type [Stream] but got [${typeDetect(value)}]`; + } + } +} diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 5c7e42d0d6f5fd..770314faa8ebd8 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -38,6 +38,7 @@ declare module 'joi' { duration: () => AnySchema; map: () => MapSchema; record: () => RecordSchema; + stream: () => AnySchema; }; interface AnySchema { diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts index 25962c91a896d7..4244006f4a3a33 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts @@ -62,8 +62,7 @@ export interface ReqOptions { query?: Record; method: 'GET' | 'POST' | 'PUT' | 'DELETE'; body?: any; - attempt?: number; - maxAttempts?: number; + retries?: number; } const delay = (ms: number) => @@ -87,44 +86,47 @@ export class KbnClientRequester { async request(options: ReqOptions): Promise { const url = Url.resolve(this.pickUrl(), options.path); const description = options.description || `${options.method} ${url}`; - const attempt = options.attempt === undefined ? 1 : options.attempt; - const maxAttempts = - options.maxAttempts === undefined ? DEFAULT_MAX_ATTEMPTS : options.maxAttempts; - - try { - const response = await Axios.request({ - method: options.method, - url, - data: options.body, - params: options.query, - headers: { - 'kbn-xsrf': 'kbn-client', - }, - }); - - return response.data; - } catch (error) { - let retryErrorMsg: string | undefined; - if (isAxiosRequestError(error)) { - retryErrorMsg = `[${description}] request failed (attempt=${attempt})`; - } else if (isConcliftOnGetError(error)) { - retryErrorMsg = `Conflict on GET (path=${options.path}, attempt=${attempt})`; - } + let attempt = 0; + const maxAttempts = options.retries ?? DEFAULT_MAX_ATTEMPTS; + + while (true) { + attempt += 1; + + try { + const response = await Axios.request({ + method: options.method, + url, + data: options.body, + params: options.query, + headers: { + 'kbn-xsrf': 'kbn-client', + }, + }); + + return response.data; + } catch (error) { + const conflictOnGet = isConcliftOnGetError(error); + const requestedRetries = options.retries !== undefined; + const failedToGetResponse = isAxiosRequestError(error); + + let errorMessage; + if (conflictOnGet) { + errorMessage = `Conflict on GET (path=${options.path}, attempt=${attempt}/${maxAttempts})`; + this.log.error(errorMessage); + } else if (requestedRetries || failedToGetResponse) { + errorMessage = `[${description}] request failed (attempt=${attempt}/${maxAttempts})`; + this.log.error(errorMessage); + } else { + throw error; + } - if (retryErrorMsg) { if (attempt < maxAttempts) { - this.log.error(retryErrorMsg); await delay(1000 * attempt); - return await this.request({ - ...options, - attempt: attempt + 1, - }); + continue; } - throw new Error(retryErrorMsg + ' and ran out of retries'); + throw new Error(`${errorMessage} -- and ran out of retries`); } - - throw error; } } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts index 03033bc5c2ccc4..ad01dea624c3c6 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts @@ -40,7 +40,7 @@ export class KbnClientUiSettings { async get(setting: string) { const all = await this.getAll(); - const value = all.settings[setting] ? all.settings[setting].userValue : undefined; + const value = all[setting]?.userValue; this.log.verbose('uiSettings.value: %j', value); return value; @@ -68,24 +68,24 @@ export class KbnClientUiSettings { * with some defaults */ async replace(doc: UiSettingValues) { - const all = await this.getAll(); - for (const [name, { isOverridden }] of Object.entries(all.settings)) { - if (!isOverridden) { - await this.unset(name); + this.log.debug('replacing kibana config doc: %j', doc); + + const changes: Record = { + ...this.defaults, + ...doc, + }; + + for (const [name, { isOverridden }] of Object.entries(await this.getAll())) { + if (!isOverridden && !changes.hasOwnProperty(name)) { + changes[name] = null; } } - this.log.debug('replacing kibana config doc: %j', doc); - await this.requester.request({ method: 'POST', path: '/api/kibana/settings', - body: { - changes: { - ...this.defaults, - ...doc, - }, - }, + body: { changes }, + retries: 5, }); } @@ -105,9 +105,11 @@ export class KbnClientUiSettings { } private async getAll() { - return await this.requester.request({ + const resp = await this.requester.request({ path: '/api/kibana/settings', method: 'GET', }); + + return resp.settings; } } diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 40a3bd475f1c1d..4c519a609d86fe 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "target", + "target": "ES2019", "declaration": true }, "include": [ diff --git a/packages/kbn-elastic-idx/.npmignore b/packages/kbn-elastic-idx/.npmignore deleted file mode 100644 index ece13b72c93eaa..00000000000000 --- a/packages/kbn-elastic-idx/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -/tsconfig.json -/src -/babel/index.test.js diff --git a/packages/kbn-elastic-idx/README.md b/packages/kbn-elastic-idx/README.md deleted file mode 100644 index 81cd5d0f86e9bb..00000000000000 --- a/packages/kbn-elastic-idx/README.md +++ /dev/null @@ -1,76 +0,0 @@ -Kibana elastic-idx Library -========================== - -The `@kbn/elastic-idx` package provides the `idx` function used for optional -chaining. Currently, the optional chaining draft is in stage 1, making it too -uncertain to add syntax support within Kibana. Other optional chaining -libraries require the Proxy object to be polyfilled for browser support, -however, this polyfill is not fully supported across all browsers that Kibana -requires. The facebookincubator `idx` project -(https://github.com/facebookincubator/idx) provides an answer to this with a -specific implementation that is understood by TypeScript so that type -information does not get lost (unlike lodash get) The `@kbn/elastic-idx` -library makes use the `idx` idiom but differs in the way null values within the -property chain are handled. - -Similar to the facebookincubator `idx` project, `@kbn/elastic-idx` also -provides the Babel plugin to transform `idx()` function calls into the expanded -form. This Babel plugin was based off the facebookincubator `idx` Babel -plugin, since the invocation syntax is almost identical, but the transformed -code differs to match how the `@kbn/elastic-idx` library treats null values. - -App Usage ----------- -Within Kibana, `@kbn/elastic-idx` can be imported and used in any JavaScript or -TypeScript project: - -``` -import { idx } from '@kbn/elastic-idx'; - -const obj0 = { a: { b: { c: { d: 'iamdefined' } } } }; -const obj1 = { a: { b: null } }; - -idx(obj0, _ => _.a.b.c.d); // returns 'iamdefined' -idx(obj1, _ => _.a.b.c.e); // returns undefined -idx(obj1, _ => _.a.b); // returns null -``` - -Build Optimization -------------------- -Similar to the facebookincubator `idx` project, it is NOT RECOMMENDED to use -idx in shipped app code. The implementation details which make -`@kbn/elastic-idx` possible comes at a non-negligible performance cost. This -usually isn't noticable during development, but for production builds, it is -recommended to transform idx calls into native, expanded form JS. Use the -plugin `@kbn/elastic-idx/babel` within your Babel configuration: - -``` -{ "plugins": [ "@kbn/elastic-idx/babel" ] } -``` - -The resulting Babel transforms the following: - -``` -import { idx } from '@kbn/elastic-idx'; -const obj = { a: { b: { c: { d: 'iamdefined' } } } }; -idx(obj, _ => _.a.b.c.d); -``` - -into this: - -``` -obj != null && -obj.a != null && -obj.a.b != null && -obj.a.b.c != null ? -obj.a.b.c.d : undefined -``` - -Note that this also removes the import statement from the source code, since it -no longer needs to be bundled. - -Testing --------- - -Tests can be run with `npm test`. This includes "functional" tests that -transform and evaluate idx calls. diff --git a/packages/kbn-elastic-idx/babel/index.js b/packages/kbn-elastic-idx/babel/index.js deleted file mode 100644 index 89171e3565530b..00000000000000 --- a/packages/kbn-elastic-idx/babel/index.js +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint strict: 0, new-cap: 0 */ - -'use strict'; -module.exports = context => { - const t = context.types; - - const idxRe = /\bidx\b/; - - function checkIdxArguments(file, node) { - const args = node.arguments; - if (args.length !== 2) { - throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.'); - } - const arrowFunction = args[1]; - if (!t.isArrowFunctionExpression(arrowFunction)) { - throw file.buildCodeFrameError( - arrowFunction, - 'The second argument supplied to `idx` must be an arrow function.' - ); - } - if (!t.isExpression(arrowFunction.body)) { - throw file.buildCodeFrameError( - arrowFunction.body, - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - } - if (arrowFunction.params.length !== 1) { - throw file.buildCodeFrameError( - arrowFunction.params[2] || arrowFunction, - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - } - const input = arrowFunction.params[0]; - if (!t.isIdentifier(input)) { - throw file.buildCodeFrameError( - arrowFunction.params[0], - 'The parameter supplied to `idx` must be an identifier.' - ); - } - } - - function checkIdxBindingNode(file, node) { - if (t.isImportDeclaration(node)) { - // E.g. `import '...'` - if (node.specifiers.length === 0) { - throw file.buildCodeFrameError(node, 'The idx import must have a value.'); - } - // E.g. `import A, {B} from '...'` - // `import A, * as B from '...'` - // `import {A, B} from '...'` - if (node.specifiers.length > 1) { - throw file.buildCodeFrameError( - node.specifiers[1], - 'The idx import must be a single specifier.' - ); - } - // `importKind` is not a property unless flow syntax is enabled. - // On specifiers, `importKind` is not "value" when it's not a type, it's - // `null`. - // E.g. `import type {...} from '...'` - // `import typeof {...} from '...'` - // `import {type ...} from '...'`. - // `import {typeof ...} from '...'` - if ( - node.importKind === 'type' || - node.importKind === 'typeof' || - node.specifiers[0].importKind === 'type' || - node.specifiers[0].importKind === 'typeof' - ) { - throw file.buildCodeFrameError(node, 'The idx import must be a value import.'); - } - } else if (t.isVariableDeclarator(node)) { - // E.g. var {idx} or var [idx] - if (!t.isIdentifier(node.id)) { - throw file.buildCodeFrameError( - node.specifiers[0], - 'The idx declaration must be an identifier.' - ); - } - } - } - - class UnsupportedNodeTypeError extends Error { - constructor(node, ...params) { - super(`Node type is not supported: ${node.type}`, ...params); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, UnsupportedNodeTypeError); - } - - this.name = 'UnsupportedNodeTypeError'; - } - } - - function getDeepProperties(node, properties = [], computedProperties = new Set()) { - if (t.isMemberExpression(node)) { - if (node.computed) { - computedProperties.add(node.property); - } - return getDeepProperties(node.object, [node.property, ...properties], computedProperties); - } else if (t.isIdentifier(node)) { - return [[node, ...properties], computedProperties]; - } - - throw new UnsupportedNodeTypeError(node); - } - - function buildMemberChain(properties, computedProperties) { - if (properties.length > 1) { - const lead = properties.slice(0, properties.length - 1); - const last = properties[properties.length - 1]; - return t.MemberExpression( - buildMemberChain(lead, computedProperties), - last, - computedProperties.has(last) - ); - } else if (properties.length === 1) { - return properties[0]; - } - return t.identifier('undefined'); - } - - function buildExpandedMemberNullChecks( - leadingProperties = [], - trailingProperties = [], - computedProperties - ) { - const propertyChainNullCheck = t.BinaryExpression( - '!=', - buildMemberChain(leadingProperties, computedProperties), - t.NullLiteral() - ); - - if (trailingProperties.length <= 1) { - return propertyChainNullCheck; - } - - const [headTrailingProperty, ...tailProperties] = trailingProperties; - - return t.LogicalExpression( - '&&', - propertyChainNullCheck, - buildExpandedMemberNullChecks( - [...leadingProperties, headTrailingProperty], - tailProperties, - computedProperties - ) - ); - } - - function buildExpandedMemberAccess(node, state) { - let baseNode; - let properties; - let computedProperties; - - try { - [[baseNode, ...properties], computedProperties] = getDeepProperties(node); - } catch (error) { - if (error instanceof UnsupportedNodeTypeError) { - throw state.file.buildCodeFrameError( - node, - 'idx callbacks may only access properties on the callback parameter.' - ); - } - - throw error; - } - - if (baseNode.name !== state.base.name) { - throw state.file.buildCodeFrameError( - node, - 'The parameter of the arrow function supplied to `idx` must match ' + - 'the base of the body expression.' - ); - } - return t.ConditionalExpression( - buildExpandedMemberNullChecks([state.input], properties, computedProperties), - buildMemberChain([state.input, ...properties], computedProperties), - t.identifier('undefined') - ); - } - - function visitIdxCallExpression(path, state) { - const node = path.node; - checkIdxArguments(state.file, node); - const replacement = buildExpandedMemberAccess(node.arguments[1].body, { - file: state.file, - input: node.arguments[0], - base: node.arguments[1].params[0], - }); - path.replaceWith(replacement); - } - - function isIdxImportOrRequire(node, name) { - if (t.isImportDeclaration(node)) { - if (name instanceof RegExp) { - return name.test(node.source.value); - } else { - return t.isStringLiteral(node.source, { value: name }); - } - } else if (t.isVariableDeclarator(node)) { - return ( - t.isCallExpression(node.init) && - t.isIdentifier(node.init.callee, { name: 'require' }) && - (name instanceof RegExp - ? name.test(node.init.arguments[0].value) - : t.isLiteral(node.init.arguments[0], { value: name })) - ); - } else { - return false; - } - } - - const declareVisitor = { - 'ImportDeclaration|VariableDeclarator'(path, state) { - if (!isIdxImportOrRequire(path.node, state.importName)) { - return; - } - - checkIdxBindingNode(state.file, path.node); - - const bindingName = t.isImportDeclaration(path.node) - ? path.node.specifiers[0].local.name - : path.node.id.name; - const idxBinding = path.scope.getOwnBinding(bindingName); - - idxBinding.constantViolations.forEach(refPath => { - throw state.file.buildCodeFrameError(refPath.node, '`idx` cannot be redefined.'); - }); - - let didTransform = false; - let didSkip = false; - - // Traverse the references backwards to process inner calls before - // outer calls. - idxBinding.referencePaths - .slice() - .reverse() - .forEach(refPath => { - if (refPath.node === idxBinding.node) { - // Do nothing... - } else if (refPath.parentPath.isMemberExpression()) { - visitIdxCallExpression(refPath.parentPath.parentPath, state); - didTransform = true; - } else if (refPath.parentPath.isCallExpression()) { - visitIdxCallExpression(refPath.parentPath, state); - didTransform = true; - } else { - // Should this throw? - didSkip = true; - } - }); - if (didTransform && !didSkip) { - path.remove(); - } - }, - }; - - return { - visitor: { - Program(path, state) { - const importName = state.opts.importName || '@kbn/elastic-idx'; - // If there can't reasonably be an idx call, exit fast. - if (importName !== '@kbn/elastic-idx' || idxRe.test(state.file.code)) { - // We're very strict about the shape of idx. Some transforms, like - // "babel-plugin-transform-async-to-generator", will convert arrow - // functions inside async functions into regular functions. So we do - // our transformation before any one else interferes. - const newState = { file: state.file, importName }; - path.traverse(declareVisitor, newState); - } - }, - }, - }; -}; diff --git a/packages/kbn-elastic-idx/babel/index.test.js b/packages/kbn-elastic-idx/babel/index.test.js deleted file mode 100644 index c4aee5266d6fa3..00000000000000 --- a/packages/kbn-elastic-idx/babel/index.test.js +++ /dev/null @@ -1,694 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -'use strict'; // eslint-disable-line strict - -jest.autoMockOff(); - -const { transformSync: babelTransform } = require('@babel/core'); -const babelPluginIdx = require('./index'); -const transformAsyncToGenerator = require('@babel/plugin-transform-async-to-generator'); -const vm = require('vm'); - -function transform(source, plugins, options) { - return babelTransform(source, { - plugins: plugins || [[babelPluginIdx, options]], - babelrc: false, - highlightCode: false, - }).code; -} - -const asyncToGeneratorHelperCode = ` -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } -} - -function _asyncToGenerator(fn) { - return function() { - var self = this, - args = arguments; - return new Promise(function(resolve, reject) { - var gen = fn.apply(self, args); - - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); - } - _next(undefined); - }); - }; -} -`; - -function stringByTrimmingSpaces(string) { - return string.replace(/\s+/g, ''); -} - -expect.extend({ - toTransformInto: (input, expected) => { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - const actual = transform(code, plugins, options); - const pass = stringByTrimmingSpaces(actual) === stringByTrimmingSpaces(expected); - return { - pass, - message: () => - 'Expected input to transform into:\n' + expected + '\n' + 'Instead, got:\n' + actual, - }; - }, - toThrowTransformError: (input, expected) => { - try { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - transform(code, plugins, options); - } catch (error) { - const actual = /^.+:\s*(.*)/.exec(error.message)[1]; // Strip "undefined: " and code snippet - return { - pass: actual === expected, - message: () => - 'Expected transform to throw "' + expected + '", but instead ' + 'got "' + actual + '".', - }; - } - return { - pass: false, - message: () => 'Expected transform to throw "' + expected + '".', - }; - }, - toReturn: (input, expected) => { - const code = transform(input, undefined); - const actual = vm.runInNewContext(code); - return { - pass: actual === expected, - message: () => 'Expected "' + expected + '" but got "' + actual + '".', - }; - }, -}); - -describe('kbn-babel-plugin-apm-idx', () => { - it('transforms member expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c.d.e); - `).toTransformInto(` - base != null && base.b != null && base.b.c != null && base.b.c.d != null - ? base.b.c.d.e - : undefined; - `); - }); - - it('throws on call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c(...foo)().d(bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms bracket notation', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"][0][c + d]); - `).toTransformInto(` - base != null && base["b"] != null && base["b"][0] != null ? base["b"][0][c + d] : undefined; - `); - }); - - it('throws on bracket notation call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"](...foo)()[0][c + d](bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms combination of both member access notations', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.a["b"].c[d[e[f]]].g); - `).toTransformInto(` - base != null && base.a != null && base.a["b"] != null && base.a["b"].c != null && base.a["b"].c[d[e[f]]] != null - ? base.a["b"].c[d[e[f]]].g - : undefined; - `); - }); - - it('transforms if the base is an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(this.props.base[5], _ => _.property); - `).toTransformInto(` - this.props.base[5] != null ? this.props.base[5].property : undefined; - `); - }); - - it('throws if the arrow function has more than one param', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, (a, b) => _.property); - `).toThrowTransformError( - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - }); - - it('throws if the arrow function has an invalid base', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, a => b.property) - `).toThrowTransformError( - 'The parameter of the arrow function supplied to `idx` must match the ' + - 'base of the body expression.' - ); - }); - - it('throws if the arrow function expression has non-properties/methods', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => (_.a++).b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('throws if the body of the arrow function is not an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => {}) - `).toThrowTransformError( - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - }); - - it('ignores non-function call idx', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `); - }); - - it('throws if idx is called with zero arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with one argument', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with three arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1, 2, 3); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('transforms idx calls as part of another expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - paddingStatement(); - a = idx(base, _ => _.b[c]); - `).toTransformInto(` - paddingStatement(); - a = base != null && base.b != null ? base.b[c] : undefined; - `); - }); - - it('transforms nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toTransformInto(` - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ) != null - && - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e != null ? - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e.f : - undefined; - `); - }); - - it('transforms idx calls inside async functions (plugin order #1)', () => { - expect({ - plugins: [babelPluginIdx, transformAsyncToGenerator], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls inside async functions (plugin order #2)', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls in async methods', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - class Foo { - async bar() { - idx(base, _ => _.b); - return this; - } - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - class Foo { - bar() { - var _this = this; - - return _asyncToGenerator(function* () { - base != null ? base.b : undefined; - return _this; - })(); - } - } - `); - }); - - it('transforms idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms deep idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms deep idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms idx calls when an idx is called as a member function on the binding in scope', () => { - expect(` - const elastic_idx = require("@kbn/elastic-idx"); - const result = elastic_idx.idx(base, _ => _.a.b.c.d); - `).toTransformInto(` - const result = base != null && - base.a != null && - base.a.b != null && - base.a.b.c != null ? - base.a.b.c.d : - undefined; - `); - }); - - it('throws on base call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _().b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms when the idx parent is a scope creating expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - (() => idx(base, _ => _.b)); - `).toTransformInto(` - () => base != null ? base.b : undefined; - `); - }); - - it('throws if redefined before use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx = null; - idx(base, _ => _.b); - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if redefined after use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - idx = null; - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if there is a duplicate declaration', () => { - expect(() => - transform(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - function idx() {} - `) - ).toThrow(); - }); - - it('handles sibling scopes with unique idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - base != null ? base.b : undefined; - } - `); - }); - - it('handles sibling scopes with and without idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - idx(base, _ => _.b); - } - `); - }); - - it('handles nested scopes with shadowing', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - function aaa() { - idx(base, _ => _.b); - function bbb(idx) { - idx(base, _ => _.b); - } - } - `).toTransformInto(` - base != null ? base.b : undefined; - function aaa() { - base != null ? base.b : undefined; - function bbb(idx) { - idx(base, _ => _.b); - } - } - `); - }); - - it('handles named idx import', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('throws on default plus named import', () => { - expect(` - import idx, {foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on default plus namespace import', () => { - expect(` - import idx, * as foo from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on named default plus other import', () => { - expect(` - import {default as idx, foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('unused idx import should be left alone', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - `); - }); - - it('allows configuration of the import name', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the import name', () => { - expect({ - code: ` - import { idx } from '@kbn/elastic-idx'; - import { idx as i_d_x } from 'i_d_x'; - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - it('allows configuration of the require name as a string', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('allows configuration of the require name as a RegExp', () => { - expect({ - code: ` - import { idx } from '../../common/idx'; - idx(base, _ => _.b); - `, - options: { importName: /.*idx$/ }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the require name', () => { - expect({ - code: ` - const idx = require('@kbn/elastic-idx'); - const i_d_x = require('i_d_x'); - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - const idx = require('@kbn/elastic-idx'); - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - describe('functional', () => { - it('works with only properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: 2}}}; - idx(base, _ => _.a.b.c); - `).toReturn(2); - }); - - it('works with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {}}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with null properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: null}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: {d: {e: {f: 2}}}}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(2); - }); - - it('works with nested idx calls with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: null}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(undefined); - }); - }); -}); diff --git a/packages/kbn-elastic-idx/package.json b/packages/kbn-elastic-idx/package.json deleted file mode 100644 index 9532983942d6b9..00000000000000 --- a/packages/kbn-elastic-idx/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@kbn/elastic-idx", - "version": "1.0.0", - "private": true, - "license": "Apache-2.0", - "description": "Library for optional chaining & the Babel plugin to transpile idx calls to plain, optimized JS", - "main": "target/index.js", - "types": "target/index.d.js", - "repository": { - "type": "git", - "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-elastic-idx" - }, - "scripts": { - "build": "tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch", - "test": "jest" - }, - "devDependencies": { - "@babel/core": "^7.5.5", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "jest": "^24.9.0", - "typescript": "3.7.2" - }, - "jest": { - "testEnvironment": "node" - } -} diff --git a/packages/kbn-elastic-idx/src/index.ts b/packages/kbn-elastic-idx/src/index.ts deleted file mode 100644 index eeb0df2747a143..00000000000000 --- a/packages/kbn-elastic-idx/src/index.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. - */ - -/** - * DeepRequiredArray - * Nested array condition handler - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface DeepRequiredArray extends Array> {} - -/** - * DeepRequiredObject - * Nested object condition handler - */ -type DeepRequiredObject = { [P in keyof T]-?: DeepRequired }; - -/** - * Function that has deeply required return type - */ -type FunctionWithRequiredReturnType any> = T extends ( - ...args: infer A -) => infer R - ? (...args: A) => DeepRequired - : never; - -/** - * DeepRequired - * Required that works for deeply nested structure - */ -type DeepRequired = NonNullable extends never - ? T - : T extends any[] - ? DeepRequiredArray - : T extends (...args: any[]) => any - ? FunctionWithRequiredReturnType - : NonNullable extends object - ? DeepRequiredObject> - : T; - -export function idx( - input: T1, - accessor: (input: NonNullable>) => T2 -): T2 | undefined { - try { - return accessor(input as NonNullable>); - } catch (error) { - return undefined; - } -} diff --git a/packages/kbn-elastic-idx/tsconfig.json b/packages/kbn-elastic-idx/tsconfig.json deleted file mode 100644 index 27833d7594031c..00000000000000 --- a/packages/kbn-elastic-idx/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "./target" - }, - "include": ["src/**/*"] -} diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 0146111941044f..bbc5126da1dce0 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -28,7 +28,7 @@ "intl-messageformat": "^2.2.0", "intl-relativeformat": "^2.1.0", "prop-types": "^15.6.2", - "react": "^16.8.6", + "react": "^16.12.0", "react-intl": "^2.8.0" } } diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index f4260d31d80fbf..ccdddc87dbc18a 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -55,7 +55,7 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), + ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), ], wait: true, env: { diff --git a/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js b/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js index db33b209951ebd..64656baee6fd2b 100644 --- a/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js +++ b/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js @@ -41,19 +41,9 @@ module.exports = function rewritePackage(buildSource, buildVersion, kibanaVersio delete pkg.scripts; delete pkg.devDependencies; - file.contents = toBuffer(JSON.stringify(pkg, null, 2)); + file.contents = Buffer.from(JSON.stringify(pkg, null, 2)); } return file; }); }; - -function toBuffer(string) { - if (typeof Buffer.from === 'function') { - return Buffer.from(string, 'utf8'); - } else { - // this was deprecated in node v5 in favor - // of Buffer.from(string, encoding) - return new Buffer(string, 'utf8'); - } -} diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index bbe12a93c241f7..aea85c13d7f325 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -23185,6 +23185,7 @@ function getProjectPaths(rootPath, options = {}) { 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/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*')); if (!ossOnly) { projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); diff --git a/packages/kbn-pm/src/config.ts b/packages/kbn-pm/src/config.ts index 4886b0c266a892..2e42a182e7ec37 100644 --- a/packages/kbn-pm/src/config.ts +++ b/packages/kbn-pm/src/config.ts @@ -44,6 +44,7 @@ export function getProjectPaths(rootPath: string, options: IProjectPathOptions = // correct and the expect behavior. projectPaths.push(resolve(rootPath, 'test/plugin_functional/plugins/*')); projectPaths.push(resolve(rootPath, 'test/interpreter_functional/plugins/*')); + projectPaths.push(resolve(rootPath, 'examples/*')); if (!ossOnly) { projectPaths.push(resolve(rootPath, 'x-pack')); diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 8c5358c82208df..0cc54fa2a64c41 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -15,7 +15,8 @@ "@kbn/dev-utils": "1.0.0", "@types/parse-link-header": "^1.0.0", "@types/strip-ansi": "^5.2.1", - "@types/xml2js": "^0.4.5" + "@types/xml2js": "^0.4.5", + "diff": "^4.0.1" }, "dependencies": { "chalk": "^2.4.2", diff --git a/packages/kbn-test/src/failed_tests_reporter/README.md b/packages/kbn-test/src/failed_tests_reporter/README.md index 1af309ba525b66..20592ecd733b62 100644 --- a/packages/kbn-test/src/failed_tests_reporter/README.md +++ b/packages/kbn-test/src/failed_tests_reporter/README.md @@ -12,10 +12,10 @@ copy(`wget "${Array.from($$('a[href$=".xml"]')).filter(a => a.innerText === 'Dow This copies a script to download the reports, which you should execute in the `test/junit` directory. -Next, run the CLI in `--dry-run` mode so that it doesn't actually communicate with Github. +Next, run the CLI in `--no-github-update` mode so that it doesn't actually communicate with Github and `--no-report-update` to prevent the script from mutating the reports on disk and instead log the updated report. ```sh -node scripts/report_failed_tests.js --verbose --dry-run --build-url foo +node scripts/report_failed_tests.js --verbose --no-github-update --no-report-update ``` -If you specify the `GITHUB_TOKEN` environment variable then `--dry-run` will execute read operations but still won't execute write operations. \ No newline at end of file +Unless you specify the `GITHUB_TOKEN` environment variable requests to read existing issues will use anonymous access which is limited to 60 requests per hour. \ No newline at end of file diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml index c006b0933bfa18..9da63234e03d46 100644 --- a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml +++ b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/ftr_report.xml @@ -41,5 +41,13 @@ Wait timed out after 10055ms + + + + diff --git a/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts new file mode 100644 index 00000000000000..02b6b5f064218e --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/__fixtures__/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const Fs = jest.requireActual('fs'); + +export const FTR_REPORT = Fs.readFileSync(require.resolve('./ftr_report.xml'), 'utf8'); +export const JEST_REPORT = Fs.readFileSync(require.resolve('./jest_report.xml'), 'utf8'); +export const KARMA_REPORT = Fs.readFileSync(require.resolve('./karma_report.xml'), 'utf8'); +export const MOCHA_REPORT = Fs.readFileSync(require.resolve('./mocha_report.xml'), 'utf8'); diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts new file mode 100644 index 00000000000000..5a7939f87130b6 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts @@ -0,0 +1,351 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Path from 'path'; + +import { ToolingLog } from '@kbn/dev-utils'; +// @ts-ignore +import { createPatch } from 'diff'; + +// turns out Jest can't encode xml diffs in their JUnit reports... +expect.addSnapshotSerializer({ + test: v => typeof v === 'string' && (v.includes('<') || v.includes('>')), + print: v => + v + .replace(//g, '›') + .replace(/^\s+$/gm, ''), +}); + +jest.mock('fs', () => { + const realFs = jest.requireActual('fs'); + return { + readFile: realFs.read, + writeFile: (...args: any[]) => { + setTimeout(args[args.length - 1], 0); + }, + }; +}); + +import { FTR_REPORT, JEST_REPORT, MOCHA_REPORT, KARMA_REPORT } from './__fixtures__'; +import { parseTestReport } from './test_report'; +import { addMessagesToReport } from './add_messages_to_report'; + +beforeEach(() => { + jest.resetAllMocks(); +}); + +const log = new ToolingLog(); + +it('rewrites ftr reports with minimal changes', async () => { + const xml = await addMessagesToReport({ + report: await parseTestReport(FTR_REPORT), + messages: [ + { + name: 'maps app maps loaded from sample data ecommerce "before all" hook', + classname: + 'Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js', + message: 'foo bar', + }, + ], + log, + reportPath: Path.resolve(__dirname, './__fixtures__/ftr_report.xml'), + }); + + expect(createPatch('ftr.xml', FTR_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(` + Index: ftr.xml + =================================================================== + --- ftr.xml [object Object] + +++ ftr.xml + @@ -1,53 +1,56 @@ + ‹?xml version="1.0" encoding="utf-8"?› + ‹testsuites› + ‹testsuite timestamp="2019-06-05T23:37:10" time="903.670" tests="129" failures="5" skipped="71"› + ‹testcase name="maps app maps loaded from sample data ecommerce "before all" hook" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js" time="154.378"› + - ‹system-out› + - ‹![CDATA[[00:00:00] │ + + ‹system-out›Failed Tests Reporter: + + - foo bar + + + + + + [00:00:00] │ + [00:07:04] └-: maps app + ... + [00:15:02] │ + -]]› + + + ‹/system-out› + ‹failure› + - ‹![CDATA[Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~="layerTocActionsPanelToggleButtonRoad_Map_-_Bright"]) + + Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~="layerTocActionsPanelToggleButtonRoad_Map_-_Bright"]) + Wait timed out after 10055ms + at /var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:834:17 + at process._tickCallback (internal/process/next_tick.js:68:7) + at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9) + - at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13)]]› + + at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13) + ‹/failure› + ‹/testcase› + ‹testcase name="maps app "after all" hook" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps" time="0.179"› + ‹system-out› + - ‹![CDATA[[00:00:00] │ + + [00:00:00] │ + [00:07:04] └-: maps app + ... + -]]› + + + ‹/system-out› + ‹failure› + - ‹![CDATA[{ NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used. + + { NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used. + at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38) + at Object.thenFinally [as finally] (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/promise.js:124:12) + - at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }]]› + + at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' } + ‹/failure› + ‹/testcase› + ‹testcase name="InfraOps app feature controls infrastructure security global infrastructure all privileges shows infrastructure navlink" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/infra/feature_controls/infrastructure_security·ts"› + ‹system-out› + - ‹![CDATA[[00:00:00] │ + + [00:00:00] │ + [00:05:13] └-: InfraOps app + ... + -]]› + + + ‹/system-out› + ‹skipped/› + ‹/testcase› + ‹testcase name="machine learning anomaly detection saved search with lucene query job creation opens the advanced section" classname="Firefox XPack UI Functional Tests.x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job·ts" time="6.040"› + - ‹system-out›‹![CDATA[[00:21:57] └-: machine learning...]]›‹/system-out› + - ‹failure›‹![CDATA[{ NoSuchSessionError: Tried to run command without establishing a connection + + ‹system-out›[00:21:57] └-: machine learning...‹/system-out› + + ‹failure›{ NoSuchSessionError: Tried to run command without establishing a connection + at Object.throwDecodedError (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/error.js:550:15) + at parseHttpResponse (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:563:13) + at Executor.execute (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:489:26) + - at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }]]›‹/failure› + + at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }‹/failure› + ‹/testcase› + ‹/testsuite› + -‹/testsuites› + +‹/testsuites› + \\ No newline at end of file + + `); +}); + +it('rewrites jest reports with minimal changes', async () => { + const xml = await addMessagesToReport({ + report: await parseTestReport(JEST_REPORT), + messages: [ + { + classname: 'X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp', + name: 'launcher can reconnect if process died', + message: 'foo bar', + }, + ], + log, + reportPath: Path.resolve(__dirname, './__fixtures__/jest_report.xml'), + }); + + expect(createPatch('jest.xml', JEST_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(` + Index: jest.xml + =================================================================== + --- jest.xml [object Object] + +++ jest.xml + @@ -3,13 +3,17 @@ + ‹testsuite name="x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts" timestamp="2019-06-07T03:42:21" time="14.504" tests="5" failures="1" skipped="0" file="/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts"› + ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can start and end a process" time="1.316"/› + ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can force kill the process if langServer can not exit" time="3.182"/› + ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can reconnect if process died" time="7.060"› + - ‹failure› + - ‹![CDATA[TypeError: Cannot read property '0' of undefined + - at Object.‹anonymous›.test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10)]]› + - ‹/failure› + + ‹failure›‹![CDATA[ + + TypeError: Cannot read property '0' of undefined + + at Object.‹anonymous›.test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10) + + ]]›‹/failure› + + ‹system-out›Failed Tests Reporter: + + - foo bar + + + +‹/system-out› + ‹/testcase› + ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="passive launcher can start and end a process" time="0.435"/› + ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="passive launcher should restart a process if a process died before connected" time="1.502"/› + ‹/testsuite› + -‹/testsuites› + +‹/testsuites› + \\ No newline at end of file + + `); +}); + +it('rewrites mocha reports with minimal changes', async () => { + const xml = await addMessagesToReport({ + report: await parseTestReport(MOCHA_REPORT), + messages: [ + { + name: 'code in multiple nodes "before all" hook', + classname: 'X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts', + message: 'foo bar', + }, + ], + log, + reportPath: Path.resolve(__dirname, './__fixtures__/mocha_report.xml'), + }); + + expect(createPatch('mocha.xml', MOCHA_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(` + Index: mocha.xml + =================================================================== + --- mocha.xml [object Object] + +++ mocha.xml + @@ -1,13 +1,16 @@ + ‹?xml version="1.0" encoding="utf-8"?› + ‹testsuites› + ‹testsuite timestamp="2019-06-13T23:29:36" time="30.739" tests="1444" failures="2" skipped="3"› + ‹testcase name="code in multiple nodes "before all" hook" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts" time="0.121"› + - ‹system-out› + - ‹![CDATA[]]› + + ‹system-out›Failed Tests Reporter: + + - foo bar + + + + + + + ‹/system-out› + - ‹failure› + - ‹![CDATA[Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable + + ‹failure›‹![CDATA[ + + Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable + ‹html› + ‹head›‹title›503 Service Temporarily Unavailable‹/title›‹/head› + ‹body bgcolor="white"› + ‹center›‹h1›503 Service Temporarily Unavailable‹/h1›‹/center› + @@ -15,24 +18,24 @@ + ‹/body› + ‹/html› + + at Function.getSnapshot (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/packages/kbn-es/src/artifact.js:95:13) + - at process._tickCallback (internal/process/next_tick.js:68:7)]]› + - ‹/failure› + + at process._tickCallback (internal/process/next_tick.js:68:7) + + ]]›‹/failure› + ‹/testcase› + ‹testcase name="code in multiple nodes "after all" hook" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts" time="0.003"› + ‹system-out› + - ‹![CDATA[]]› + + + ‹/system-out› + ‹failure› + - ‹![CDATA[TypeError: Cannot read property 'shutdown' of undefined + + TypeError: Cannot read property 'shutdown' of undefined + at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23) + - at process.topLevelDomainCallback (domain.js:120:23)]]› + + at process.topLevelDomainCallback (domain.js:120:23) + ‹/failure› + ‹/testcase› + ‹testcase name="repository service test can not clone a repo by ssh without a key" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/repository_service·ts" time="0.005"› + ‹system-out› + - ‹![CDATA[]]› + + + ‹/system-out› + ‹/testcase› + ‹/testsuite› + -‹/testsuites› + +‹/testsuites› + \\ No newline at end of file + + `); +}); + +it('rewrites karma reports with minimal changes', async () => { + const xml = await addMessagesToReport({ + report: await parseTestReport(KARMA_REPORT), + messages: [ + { + name: + 'CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK', + classname: 'Browser Unit Tests.CoordinateMapsVisualizationTest', + message: 'foo bar', + }, + ], + log, + reportPath: Path.resolve(__dirname, './__fixtures__/karma_report.xml'), + }); + + expect(createPatch('karma.xml', KARMA_REPORT, xml, { context: 0 })).toMatchInlineSnapshot(` + Index: karma.xml + =================================================================== + --- karma.xml [object Object] + +++ karma.xml + @@ -1,5 +1,5 @@ + -‹?xml version="1.0"?› + +‹?xml version="1.0" encoding="utf-8"?› + ‹testsuite name="Chrome 75.0.3770 (Mac OS X 10.14.5)" package="" timestamp="2019-07-02T19:53:21" id="0" hostname="spalger.lan" tests="648" errors="0" failures="4" time="1.759"› + ‹properties› + ‹property name="browser.fullName" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"/› + ‹/properties› + @@ -7,27 +7,31 @@ + ‹testcase name="Vis-Editor-Agg-Params plugin directive should hide custom label parameter" time="0" classname="Browser Unit Tests.Vis-Editor-Agg-Params plugin directive"› + ‹skipped/› + ‹/testcase› + ‹testcase name="CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK" time="0.265" classname="Browser Unit Tests.CoordinateMapsVisualizationTest"› + - ‹failure type=""›Error: expected 7069 to be below 64 + - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) + - at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) + - at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) + - at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) + + ‹failure type=""›‹![CDATA[Error: expected 7069 to be below 64 + + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.assert (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13671:11) + + at Assertion.__kbnBundles__.tests../packages/kbn-expect/expect.js.Assertion.lessThan.Assertion.below (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:13891:8) + + at Function.lessThan (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:14078:15) + + at _callee3$ (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158985:60) + at tryCatch (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:62:40) + at Generator.invoke [as _invoke] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:288:22) + - at Generator.prototype.<computed> [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) + - at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) + - at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) + -‹/failure› + + at Generator.prototype.‹computed› [as next] (webpack://%5Bname%5D/./node_modules/regenerator-runtime/runtime.js?:114:21) + + at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) + + at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) + +]]›‹/failure› + + ‹system-out›Failed Tests Reporter: + + - foo bar + + + +‹/system-out› + ‹/testcase› + ‹testcase name="CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should toggle to Heatmap OK" time="0.055" classname="Browser Unit Tests.CoordinateMapsVisualizationTest"/› + ‹testcase name="VegaParser._parseSchema should warn on vega-lite version too new to be supported" time="0.001" classname="Browser Unit Tests.VegaParser·_parseSchema"/› + ‹system-out› + - ‹![CDATA[Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'ready to load tests for shard 1 of 4' + + Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'ready to load tests for shard 1 of 4' + ,Chrome 75.0.3770 (Mac OS X 10.14.5) WARN: 'Unmatched GET to http://localhost:9876/api/interpreter/fns' + ... + + -]]› + + + ‹/system-out› + ‹system-err/› + -‹/testsuite› + +‹/testsuite› + \\ No newline at end of file + + `); +}); diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts new file mode 100644 index 00000000000000..32ea5fa0f90339 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.ts @@ -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 Fs from 'fs'; +import { promisify } from 'util'; + +import { ToolingLog } from '@kbn/dev-utils'; +import xml2js from 'xml2js'; + +import { TestReport, makeFailedTestCaseIter } from './test_report'; + +const writeAsync = promisify(Fs.writeFile); + +export interface Message { + classname: string; + name: string; + message: string; +} + +/** + * Mutate the report to include mentions of Github issues related to test failures, + * then write the updated report to disk + */ +export async function addMessagesToReport(options: { + log: ToolingLog; + report: TestReport; + messages: Message[]; + reportPath: string; + dryRun?: boolean; +}) { + const { log, report, messages, reportPath, dryRun } = options; + + for (const testCase of makeFailedTestCaseIter(report)) { + const { classname, name } = testCase.$; + const messageList = messages + .filter(u => u.classname === classname && u.name === name) + .reduce((acc, u) => `${acc}\n - ${u.message}`, ''); + + if (!messageList) { + continue; + } + + log.info(`${classname} - ${name}:${messageList}`); + const output = `Failed Tests Reporter:${messageList}\n\n`; + + if (!testCase['system-out']) { + testCase['system-out'] = [output]; + } else if (typeof testCase['system-out'][0] === 'string') { + testCase['system-out'][0] = output + String(testCase['system-out'][0]); + } else { + testCase['system-out'][0]._ = output + testCase['system-out'][0]._; + } + } + + const builder = new xml2js.Builder({ + cdata: true, + xmldec: { version: '1.0', encoding: 'utf-8' }, + }); + + const xml = builder + .buildObject(report) + .split('\n') + .map(line => (line.trim() === '' ? '' : line)) + .join('\n'); + + if (dryRun) { + log.info(`updated ${reportPath}\n${xml}`); + } else { + await writeAsync(reportPath, xml, 'utf8'); + } + return xml; +} diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts index 1e0514a9b1cb07..fe6e0bbc796eed 100644 --- a/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.test.ts @@ -17,14 +17,12 @@ * under the License. */ -import { ToolingLog } from '@kbn/dev-utils'; - import { getFailures } from './get_failures'; - -const log = new ToolingLog(); +import { parseTestReport } from './test_report'; +import { FTR_REPORT, JEST_REPORT, KARMA_REPORT, MOCHA_REPORT } from './__fixtures__'; it('discovers failures in ftr report', async () => { - const failures = await getFailures(log, require.resolve('./__fixtures__/ftr_report.xml')); + const failures = getFailures(await parseTestReport(FTR_REPORT)); expect(failures).toMatchInlineSnapshot(` Array [ Object { @@ -37,15 +35,39 @@ it('discovers failures in ftr report', async () => { at lastError (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:28:9) at onFailure (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/test/common/services/retry/retry_for_success.ts:68:13) ", + "likelyIrrelevant": false, "name": "maps app maps loaded from sample data ecommerce \\"before all\\" hook", "time": "154.378", }, + Object { + "classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps", + "failure": " + { NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used. + at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38) + at Object.thenFinally [as finally] (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/promise.js:124:12) + at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' } + ", + "likelyIrrelevant": true, + "name": "maps app \\"after all\\" hook", + "time": "0.179", + }, + Object { + "classname": "Firefox XPack UI Functional Tests.x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job·ts", + "failure": "{ NoSuchSessionError: Tried to run command without establishing a connection + at Object.throwDecodedError (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/error.js:550:15) + at parseHttpResponse (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:563:13) + at Executor.execute (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:489:26) + at process._tickCallback (internal/process/next_tick.js:68:7) name: 'NoSuchSessionError', remoteStacktrace: '' }", + "likelyIrrelevant": true, + "name": "machine learning anomaly detection saved search with lucene query job creation opens the advanced section", + "time": "6.040", + }, ] `); }); it('discovers failures in jest report', async () => { - const failures = await getFailures(log, require.resolve('./__fixtures__/jest_report.xml')); + const failures = getFailures(await parseTestReport(JEST_REPORT)); expect(failures).toMatchInlineSnapshot(` Array [ Object { @@ -54,6 +76,7 @@ it('discovers failures in jest report', async () => { TypeError: Cannot read property '0' of undefined at Object..test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10) ", + "likelyIrrelevant": false, "name": "launcher can reconnect if process died", "time": "7.060", }, @@ -62,7 +85,7 @@ it('discovers failures in jest report', async () => { }); it('discovers failures in karma report', async () => { - const failures = await getFailures(log, require.resolve('./__fixtures__/karma_report.xml')); + const failures = getFailures(await parseTestReport(KARMA_REPORT)); expect(failures).toMatchInlineSnapshot(` Array [ Object { @@ -78,6 +101,7 @@ it('discovers failures in karma report', async () => { at asyncGeneratorStep (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158772:103) at _next (http://localhost:5610/bundles/tests.bundle.js?shards=4&shard_num=1:158774:194) ", + "likelyIrrelevant": false, "name": "CoordinateMapsVisualizationTest CoordinateMapsVisualization - basics should initialize OK", "time": "0.265", }, @@ -86,6 +110,39 @@ it('discovers failures in karma report', async () => { }); it('discovers failures in mocha report', async () => { - const failures = await getFailures(log, require.resolve('./__fixtures__/mocha_report.xml')); - expect(failures).toMatchInlineSnapshot(`Array []`); + const failures = getFailures(await parseTestReport(MOCHA_REPORT)); + expect(failures).toMatchInlineSnapshot(` + Array [ + Object { + "classname": "X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts", + "failure": " + Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable + + 503 Service Temporarily Unavailable + +

503 Service Temporarily Unavailable

+
nginx/1.13.7
+ + + + at Function.getSnapshot (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/packages/kbn-es/src/artifact.js:95:13) + at process._tickCallback (internal/process/next_tick.js:68:7) + ", + "likelyIrrelevant": true, + "name": "code in multiple nodes \\"before all\\" hook", + "time": "0.121", + }, + Object { + "classname": "X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts", + "failure": " + TypeError: Cannot read property 'shutdown' of undefined + at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23) + at process.topLevelDomainCallback (domain.js:120:23) + ", + "likelyIrrelevant": true, + "name": "code in multiple nodes \\"after all\\" hook", + "time": "0.003", + }, + ] + `); }); diff --git a/packages/kbn-test/src/failed_tests_reporter/get_failures.ts b/packages/kbn-test/src/failed_tests_reporter/get_failures.ts index d38d118a255e7e..be058791f737a0 100644 --- a/packages/kbn-test/src/failed_tests_reporter/get_failures.ts +++ b/packages/kbn-test/src/failed_tests_reporter/get_failures.ts @@ -17,69 +17,16 @@ * under the License. */ -import { promisify } from 'util'; -import Fs from 'fs'; - -import xml2js from 'xml2js'; import stripAnsi from 'strip-ansi'; -import { ToolingLog } from '@kbn/dev-utils'; - -type TestReport = - | { - testsuites: { - testsuite: TestSuite[]; - }; - } - | { - testsuite: TestSuite; - }; - -interface TestSuite { - $: { - /* ISO8601 timetamp when test suite ran */ - timestamp: string; - /* number of second this tests suite took */ - time: string; - /* number of tests as a string */ - tests: string; - /* number of failed tests as a string */ - failures: string; - /* number of skipped tests as a string */ - skipped: string; - }; - testcase: TestCase[]; -} -interface TestCase { - $: { - /* unique test name */ - name: string; - /* somewhat human readable combination of test name and file */ - classname: string; - /* number of seconds this test took */ - time: string; - }; - /* contents of system-out elements */ - 'system-out'?: string[]; - /* contents of failure elements */ - failure?: Array; - /* contents of skipped elements */ - skipped?: string[]; -} +import { FailedTestCase, TestReport, makeFailedTestCaseIter } from './test_report'; -export type TestFailure = TestCase['$'] & { +export type TestFailure = FailedTestCase['$'] & { failure: string; + likelyIrrelevant: boolean; }; -const readAsync = promisify(Fs.readFile); - -const indent = (text: string) => - ` ${text - .split('\n') - .map(l => ` ${l}`) - .join('\n')}`; - -const getFailureText = (failure: NonNullable) => { +const getFailureText = (failure: FailedTestCase['failure']) => { const [failureNode] = failure; if (failureNode && typeof failureNode === 'object' && typeof failureNode._ === 'string') { @@ -89,9 +36,10 @@ const getFailureText = (failure: NonNullable) => { return stripAnsi(String(failureNode)); }; -const isLikelyIrrelevant = ({ name, failure }: TestFailure) => { +const isLikelyIrrelevant = (name: string, failure: string) => { if ( - failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID') + failure.includes('NoSuchSessionError: This driver instance does not have a valid session ID') || + failure.includes('NoSuchSessionError: Tried to run command without establishing a connection') ) { return true; } @@ -117,47 +65,25 @@ const isLikelyIrrelevant = ({ name, failure }: TestFailure) => { if (failure.includes('Unable to fetch Kibana status API response from Kibana')) { return true; } -}; - -export async function getFailures(log: ToolingLog, testReportPath: string) { - const xml = await readAsync(testReportPath, 'utf8'); - - // Parses junit XML files - const report: TestReport = await xml2js.parseStringPromise(xml); - // Grab the failures. Reporters may report multiple testsuites in a single file. - const testSuites = 'testsuites' in report ? report.testsuites.testsuite : [report.testsuite]; + return false; +}; +export function getFailures(report: TestReport) { const failures: TestFailure[] = []; - for (const testSuite of testSuites) { - for (const testCase of testSuite.testcase) { - const { failure } = testCase; - if (!failure) { - continue; - } + for (const testCase of makeFailedTestCaseIter(report)) { + const failure = getFailureText(testCase.failure); + const likelyIrrelevant = isLikelyIrrelevant(testCase.$.name, failure); + failures.push({ // unwrap xml weirdness - const failureCase: TestFailure = { - ...testCase.$, - // Strip ANSI color characters - failure: getFailureText(failure), - }; - - if (isLikelyIrrelevant(failureCase)) { - log.warning( - `Ignoring likely irrelevant failure: ${failureCase.classname} - ${ - failureCase.name - }\n${indent(failureCase.failure)}` - ); - continue; - } - - failures.push(failureCase); - } + ...testCase.$, + // Strip ANSI color characters + failure, + likelyIrrelevant, + }); } - log.info(`Found ${failures.length} test failures`); - return failures; } diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-test/src/failed_tests_reporter/github_api.ts index 46278044c840db..d8a952bee42e59 100644 --- a/packages/kbn-test/src/failed_tests_reporter/github_api.ts +++ b/packages/kbn-test/src/failed_tests_reporter/github_api.ts @@ -19,7 +19,7 @@ import Url from 'url'; -import Axios, { AxiosRequestConfig } from 'axios'; +import Axios, { AxiosRequestConfig, AxiosInstance } from 'axios'; import parseLinkHeader from 'parse-link-header'; import { ToolingLog, isAxiosResponseError, isAxiosRequestError } from '@kbn/dev-utils'; @@ -40,25 +40,34 @@ type RequestOptions = AxiosRequestConfig & { }; export class GithubApi { - private readonly x = Axios.create({ - headers: { - Authorization: `token ${this.token}`, - 'User-Agent': 'elastic/kibana#failed_test_reporter', - }, - }); + private readonly log: ToolingLog; + private readonly token: string | undefined; + private readonly dryRun: boolean; + private readonly x: AxiosInstance; /** * Create a GithubApi helper object, if token is undefined requests won't be * sent, but will instead be logged. */ - constructor( - private readonly log: ToolingLog, - private readonly token: string | undefined, - private readonly dryRun: boolean - ) { - if (!token && !dryRun) { + constructor(options: { + log: GithubApi['log']; + token: GithubApi['token']; + dryRun: GithubApi['dryRun']; + }) { + this.log = options.log; + this.token = options.token; + this.dryRun = options.dryRun; + + if (!this.token && !this.dryRun) { throw new TypeError('token parameter is required'); } + + this.x = Axios.create({ + headers: { + ...(this.token ? { Authorization: `token ${this.token}` } : {}), + 'User-Agent': 'elastic/kibana#failed_test_reporter', + }, + }); } private failedTestIssuesPageCache: { diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts index 0e9f8db587cb70..ef6ab3c51ab194 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts @@ -18,19 +18,14 @@ */ import dedent from 'dedent'; -import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/dev-utils'; -import { createFailureIssue, updatedFailureIssue } from './report_failure'; +import { createFailureIssue, updateFailureIssue } from './report_failure'; jest.mock('./github_api'); const { GithubApi } = jest.requireMock('./github_api'); describe('createFailureIssue()', () => { it('creates new github issue with failure text, link to issue, and valid metadata', async () => { - const log = new ToolingLog(); - const writer = new ToolingLogCollectingWriter(); - log.setWriters([writer]); - const api = new GithubApi(); await createFailureIssue( @@ -40,8 +35,8 @@ describe('createFailureIssue()', () => { failure: 'this is the failure text', name: 'test name', time: '2018-01-01T01:00:00Z', + likelyIrrelevant: false, }, - log, api ); @@ -72,23 +67,14 @@ describe('createFailureIssue()', () => { ], } `); - expect(writer.messages).toMatchInlineSnapshot(` - Array [ - " info Created issue undefined", - ] - `); }); }); -describe('updatedFailureIssue()', () => { +describe('updateFailureIssue()', () => { it('increments failure count and adds new comment to issue', async () => { - const log = new ToolingLog(); - const writer = new ToolingLogCollectingWriter(); - log.setWriters([writer]); - const api = new GithubApi(); - await updatedFailureIssue( + await updateFailureIssue( 'https://build-url', { html_url: 'https://github.com/issues/1234', @@ -101,7 +87,6 @@ describe('updatedFailureIssue()', () => { " `, }, - log, api ); @@ -139,10 +124,5 @@ describe('updatedFailureIssue()', () => { ], } `); - expect(writer.messages).toMatchInlineSnapshot(` - Array [ - " info Updated issue https://github.com/issues/1234, failCount: 11", - ] - `); }); }); diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts index afb46c429c7fc6..97e9d517576fc6 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts @@ -17,18 +17,11 @@ * under the License. */ -import { ToolingLog } from '@kbn/dev-utils'; - import { TestFailure } from './get_failures'; import { GithubIssue, GithubApi } from './github_api'; import { getIssueMetadata, updateIssueMetadata } from './issue_metadata'; -export async function createFailureIssue( - buildUrl: string, - failure: TestFailure, - log: ToolingLog, - api: GithubApi -) { +export async function createFailureIssue(buildUrl: string, failure: TestFailure, api: GithubApi) { const title = `Failing test: ${failure.classname} - ${failure.name}`; const body = updateIssueMetadata( @@ -48,16 +41,10 @@ export async function createFailureIssue( } ); - const newIssueUrl = await api.createIssue(title, body, ['failed-test']); - log.info(`Created issue ${newIssueUrl}`); + return await api.createIssue(title, body, ['failed-test']); } -export async function updatedFailureIssue( - buildUrl: string, - issue: GithubIssue, - log: ToolingLog, - api: GithubApi -) { +export async function updateFailureIssue(buildUrl: string, issue: GithubIssue, api: GithubApi) { // Increment failCount const newCount = getIssueMetadata(issue.body, 'test.failCount', 0) + 1; const newBody = updateIssueMetadata(issue.body, { @@ -67,5 +54,5 @@ export async function updatedFailureIssue( await api.editIssueBodyAndEnsureOpen(issue.number, newBody); await api.addIssueComment(issue.number, `New failure: [Jenkins Build](${buildUrl})`); - log.info(`Updated issue ${issue.html_url}, failCount: ${newCount}`); + return newCount; } diff --git a/packages/kbn-test/src/failed_tests_reporter/report_metadata.ts b/packages/kbn-test/src/failed_tests_reporter/report_metadata.ts new file mode 100644 index 00000000000000..aad4c5d3c30c02 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/report_metadata.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 { TestReport, makeTestCaseIter } from './test_report'; +import { Message } from './add_messages_to_report'; + +export function* getMetadataIter(report: TestReport) { + for (const testCase of makeTestCaseIter(report)) { + if (!testCase.$['metadata-json']) { + yield [{}, testCase]; + } else { + yield [{}, JSON.parse(testCase.$['metadata-json'])]; + } + } +} + +export function getReportMessages(report: TestReport) { + const messages: Message[] = []; + for (const [metadata, testCase] of getMetadataIter(report)) { + for (const message of metadata.messages || []) { + messages.push({ + classname: testCase.$.classname, + name: testCase.$.name, + message, + }); + } + + for (const screenshot of metadata.screenshots || []) { + messages.push({ + classname: testCase.$.classname, + name: testCase.$.name, + message: `Screenshot: ${screenshot.name} ${screenshot.url}`, + }); + } + } + return messages; +} diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index 0eea1d32e5c2ec..fd7976d6e87e15 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -22,19 +22,23 @@ import globby from 'globby'; import { getFailures } from './get_failures'; import { GithubApi } from './github_api'; -import { updatedFailureIssue, createFailureIssue } from './report_failure'; +import { updateFailureIssue, createFailureIssue } from './report_failure'; import { getIssueMetadata } from './issue_metadata'; +import { readTestReport } from './test_report'; +import { addMessagesToReport } from './add_messages_to_report'; +import { getReportMessages } from './report_metadata'; export function runFailedTestsReporterCli() { run( async ({ log, flags }) => { - const buildUrl = flags['build-url']; - if (typeof buildUrl !== 'string' || !buildUrl) { - throw createFlagError('Missing --build-url or process.env.BUILD_URL'); + let updateGithub = flags['github-update']; + if (updateGithub && !process.env.GITHUB_TOKEN) { + throw createFailError( + 'GITHUB_TOKEN environment variable must be set, otherwise use --no-github-update flag' + ); } - const dryRun = !!flags['dry-run']; - if (!dryRun) { + if (updateGithub) { // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; @@ -48,26 +52,48 @@ export function runFailedTestsReporterCli() { const isMasterOrVersion = branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/); if (!isMasterOrVersion || isPr) { - throw createFailError('Failure issues only created on master/version branch jobs', { - exitCode: 0, - }); + log.info('Failure issues only created on master/version branch jobs'); + updateGithub = false; } + } - if (!process.env.GITHUB_TOKEN) { - throw createFailError( - 'GITHUB_TOKEN environment variable must be set, otherwise use --dry-run flag' - ); - } + const githubApi = new GithubApi({ + log, + token: process.env.GITHUB_TOKEN, + dryRun: !updateGithub, + }); + + const buildUrl = flags['build-url'] || (updateGithub ? '' : 'http://buildUrl'); + if (typeof buildUrl !== 'string' || !buildUrl) { + throw createFlagError('Missing --build-url or process.env.BUILD_URL'); } - const githubApi = new GithubApi(log, process.env.GITHUB_TOKEN, dryRun); const reportPaths = await globby(['target/junit/**/*.xml'], { cwd: REPO_ROOT, absolute: true, }); for (const reportPath of reportPaths) { - for (const failure of await getFailures(log, reportPath)) { + const report = await readTestReport(reportPath); + const messages = getReportMessages(report); + + for (const failure of await getFailures(report)) { + const pushMessage = (msg: string) => { + messages.push({ + classname: failure.classname, + name: failure.name, + message: msg, + }); + }; + + if (failure.likelyIrrelevant) { + pushMessage( + 'Failure is likely irrelevant' + + (updateGithub ? ', so an issue was not created or updated' : '') + ); + continue; + } + const existingIssue = await githubApi.findFailedTestIssue( i => getIssueMetadata(i.body, 'test.class') === failure.classname && @@ -75,23 +101,45 @@ export function runFailedTestsReporterCli() { ); if (existingIssue) { - await updatedFailureIssue(buildUrl, existingIssue, log, githubApi); - } else { - await createFailureIssue(buildUrl, failure, log, githubApi); + const newFailureCount = await updateFailureIssue(buildUrl, existingIssue, githubApi); + const url = existingIssue.html_url; + pushMessage(`Test has failed ${newFailureCount - 1} times on tracked branches: ${url}`); + if (updateGithub) { + pushMessage(`Updated existing issue: ${url} (fail count: ${newFailureCount})`); + } + continue; + } + + const newIssueUrl = await createFailureIssue(buildUrl, failure, githubApi); + pushMessage('Test has not failed recently on tracked branches'); + if (updateGithub) { + pushMessage(`Created new issue: ${newIssueUrl}`); } } + + // mutates report to include messages and writes updated report to disk + await addMessagesToReport({ + report, + messages, + log, + reportPath, + dryRun: !flags['report-update'], + }); } }, { description: `a cli that opens issues or updates existing issues based on junit reports`, flags: { - boolean: ['dry-run'], + boolean: ['github-update', 'report-update'], string: ['build-url'], default: { + 'github-update': true, + 'report-update': true, 'build-url': process.env.BUILD_URL, }, help: ` - --dry-run Execute the CLI without contacting Github + --no-github-update Execute the CLI without writing to Github + --no-report-update Execute the CLI without writing to the JUnit reports --build-url URL of the failed build, defaults to process.env.BUILD_URL `, }, diff --git a/packages/kbn-test/src/failed_tests_reporter/test_report.ts b/packages/kbn-test/src/failed_tests_reporter/test_report.ts new file mode 100644 index 00000000000000..6b759ef1d4c626 --- /dev/null +++ b/packages/kbn-test/src/failed_tests_reporter/test_report.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 Fs from 'fs'; +import { promisify } from 'util'; + +import xml2js from 'xml2js'; + +const readAsync = promisify(Fs.readFile); + +export type TestReport = + | { + testsuites: { + testsuite: TestSuite[]; + }; + } + | { + testsuite: TestSuite; + }; + +export interface TestSuite { + $: { + /* ISO8601 timetamp when test suite ran */ + timestamp: string; + /* number of second this tests suite took */ + time: string; + /* number of tests as a string */ + tests: string; + /* number of failed tests as a string */ + failures: string; + /* number of skipped tests as a string */ + skipped: string; + }; + testcase: TestCase[]; +} + +export interface TestCase { + $: { + /* unique test name */ + name: string; + /* somewhat human readable combination of test name and file */ + classname: string; + /* number of seconds this test took */ + time: string; + /* optional JSON encoded metadata */ + 'metadata-json'?: string; + }; + /* contents of system-out elements */ + 'system-out'?: Array; + /* contents of failure elements */ + failure?: Array; + /* contents of skipped elements */ + skipped?: Array; +} + +export interface FailedTestCase extends TestCase { + failure: Array; +} + +/** + * Parse JUnit XML Files + */ +export async function parseTestReport(xml: string): Promise { + return await xml2js.parseStringPromise(xml); +} + +export async function readTestReport(testReportPath: string) { + return await parseTestReport(await readAsync(testReportPath, 'utf8')); +} + +export function* makeTestCaseIter(report: TestReport) { + // Reporters may report multiple testsuites in a single file. + const testSuites = 'testsuites' in report ? report.testsuites.testsuite : [report.testsuite]; + + for (const testSuite of testSuites) { + for (const testCase of testSuite.testcase) { + yield testCase; + } + } +} + +export function* makeFailedTestCaseIter(report: TestReport) { + for (const testCase of makeTestCaseIter(report)) { + if (!testCase.failure) { + continue; + } + + yield testCase as FailedTestCase; + } +} diff --git a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js index 3ff674c89682d9..37ea49172d2c46 100644 --- a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js +++ b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js @@ -29,18 +29,19 @@ export default function () { services: { hookIntoLIfecycle({ getService }) { const log = getService('log'); + const lifecycle = getService('lifecycle') - getService('lifecycle') - .on('testFailure', async (err, test) => { - log.info('testFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); - }) - .on('testHookFailure', async (err, test) => { - log.info('testHookFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); - }); + lifecycle.testFailure.add(async (err, test) => { + log.info('testFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); + + lifecycle.testHookFailure.add(async (err, test) => { + log.info('testHookFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); } }, mochaReporter: { diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 459c52997e2297..e566a9a4af2628 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -18,10 +18,12 @@ */ import { ToolingLog } from '@kbn/dev-utils'; -import { Suite, Test } from './fake_mocha_types'; +import { Suite, Test } from './fake_mocha_types'; import { - createLifecycle, + Lifecycle, + LifecyclePhase, + FailureMetadata, readConfigFile, ProviderCollection, readProviderSpec, @@ -31,7 +33,8 @@ import { } from './lib'; export class FunctionalTestRunner { - public readonly lifecycle = createLifecycle(); + public readonly lifecycle = new Lifecycle(); + public readonly failureMetadata = new FailureMetadata(this.lifecycle); private closed = false; constructor( @@ -39,13 +42,12 @@ export class FunctionalTestRunner { private readonly configFile: string, private readonly configOverrides: any ) { - this.lifecycle.on('phaseStart', name => { - log.verbose('starting %j lifecycle phase', name); - }); - - this.lifecycle.on('phaseEnd', name => { - log.verbose('ending %j lifecycle phase', name); - }); + for (const [key, value] of Object.entries(this.lifecycle)) { + if (value instanceof LifecyclePhase) { + value.before$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + value.after$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + } + } } async run() { @@ -59,7 +61,7 @@ export class FunctionalTestRunner { await providers.loadAll(); const mocha = await setupMocha(this.lifecycle, this.log, config, providers); - await this.lifecycle.trigger('beforeTests'); + await this.lifecycle.beforeTests.trigger(); this.log.info('Starting tests'); return await runTests(this.lifecycle, mocha); @@ -114,6 +116,7 @@ export class FunctionalTestRunner { const coreProviders = readProviderSpec('Service', { lifecycle: () => this.lifecycle, log: () => this.log, + failureMetadata: () => this.failureMetadata, config: () => config, }); @@ -140,6 +143,6 @@ export class FunctionalTestRunner { if (this.closed) return; this.closed = true; - await this.lifecycle.trigger('cleanup'); + await this.lifecycle.cleanup.trigger(); } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts new file mode 100644 index 00000000000000..7ae46ef6fac1e0 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.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 { Lifecycle } from './lifecycle'; +import { FailureMetadata } from './failure_metadata'; + +it('collects metadata for the current test', async () => { + const lifecycle = new Lifecycle(); + const failureMetadata = new FailureMetadata(lifecycle); + + const test1 = {}; + await lifecycle.beforeEachTest.trigger(test1); + failureMetadata.add({ foo: 'bar' }); + + expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` + Object { + "foo": "bar", + } + `); + + const test2 = {}; + await lifecycle.beforeEachTest.trigger(test2); + failureMetadata.add({ test: 2 }); + + expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` + Object { + "foo": "bar", + } + `); + expect(failureMetadata.get(test2)).toMatchInlineSnapshot(` + Object { + "test": 2, + } + `); +}); + +it('adds messages to the messages state', () => { + const lifecycle = new Lifecycle(); + const failureMetadata = new FailureMetadata(lifecycle); + + const test1 = {}; + lifecycle.beforeEachTest.trigger(test1); + failureMetadata.addMessages(['foo', 'bar']); + failureMetadata.addMessages(['baz']); + + expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` + Object { + "messages": Array [ + "foo", + "bar", + "baz", + ], + } + `); +}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts new file mode 100644 index 00000000000000..9dc58d5b0b21f8 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.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 Path from 'path'; + +import { REPO_ROOT } from '@kbn/dev-utils'; + +import { Lifecycle } from './lifecycle'; + +interface Metadata { + [key: string]: unknown; +} + +export class FailureMetadata { + // mocha's global types mean we can't import Mocha or it will override the global jest types.............. + private currentTest?: any; + private readonly allMetadata = new Map(); + + constructor(lifecycle: Lifecycle) { + if (!process.env.GCS_UPLOAD_PREFIX && process.env.CI) { + throw new Error( + 'GCS_UPLOAD_PREFIX environment variable is not set and must always be set on CI' + ); + } + + lifecycle.beforeEachTest.add(test => { + this.currentTest = test; + }); + } + + add(metadata: Metadata | ((current: Metadata) => Metadata)) { + if (!this.currentTest) { + throw new Error('no current test to associate metadata with'); + } + + const current = this.allMetadata.get(this.currentTest); + this.allMetadata.set(this.currentTest, { + ...current, + ...(typeof metadata === 'function' ? metadata(current || {}) : metadata), + }); + } + + addMessages(messages: string[]) { + this.add(current => ({ + messages: [...(Array.isArray(current.messages) ? current.messages : []), ...messages], + })); + } + + /** + * @param name Name to label the URL with + * @param repoPath absolute path, within the repo, that will be uploaded + */ + addScreenshot(name: string, repoPath: string) { + const prefix = process.env.GCS_UPLOAD_PREFIX; + + if (!prefix) { + return; + } + + const slash = prefix.endsWith('/') ? '' : '/'; + const urlPath = Path.relative(REPO_ROOT, repoPath) + .split(Path.sep) + .map(c => encodeURIComponent(c)) + .join('/'); + + if (urlPath.startsWith('..')) { + throw new Error( + `Only call addUploadLink() with paths that are within the repo root, received ${repoPath} and repo root is ${REPO_ROOT}` + ); + } + + const url = `https://storage.googleapis.com/${prefix}${slash}${urlPath}`; + const screenshot = { + name, + url, + }; + + this.add(current => ({ + screenshots: [...(Array.isArray(current.screenshots) ? current.screenshots : []), screenshot], + })); + + return screenshot; + } + + get(test: any) { + return this.allMetadata.get(test); + } +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index 88995e9acc5fea..8940eccad503a4 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -17,7 +17,9 @@ * under the License. */ -export { createLifecycle, Lifecycle } from './lifecycle'; +export { Lifecycle } from './lifecycle'; +export { LifecyclePhase } from './lifecycle_phase'; export { readConfigFile, Config } from './config'; export { readProviderSpec, ProviderCollection, Provider } from './providers'; export { runTests, setupMocha } from './mocha'; +export { FailureMetadata } from './failure_metadata'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 2d9629a436b3a7..7f78bc28c6d3d9 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -17,64 +17,22 @@ * under the License. */ -import * as Rx from 'rxjs'; - -type Listener = (...args: any[]) => Promise | void; -export type Lifecycle = ReturnType; - -export function createLifecycle() { - const listeners = { - beforeLoadTests: [] as Listener[], - beforeTests: [] as Listener[], - beforeTestSuite: [] as Listener[], - beforeEachTest: [] as Listener[], - afterTestSuite: [] as Listener[], - testFailure: [] as Listener[], - testHookFailure: [] as Listener[], - cleanup: [] as Listener[], - phaseStart: [] as Listener[], - phaseEnd: [] as Listener[], - }; - - const cleanup$ = new Rx.ReplaySubject(1); - - return { - cleanup$: cleanup$.asObservable(), - - on(name: keyof typeof listeners, fn: Listener) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - listeners[name].push(fn); - return this; - }, - - async trigger(name: keyof typeof listeners, ...args: any[]) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - if (name === 'cleanup') { - if (cleanup$.closed) { - return; - } - - cleanup$.next(); - cleanup$.complete(); - } - - try { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseStart', name); - } - - await Promise.all(listeners[name].map(async fn => await fn(...args))); - } finally { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseEnd', name); - } - } - }, - }; +import { LifecyclePhase } from './lifecycle_phase'; + +// mocha's global types mean we can't import Mocha or it will override the global jest types.............. +type ItsASuite = any; +type ItsATest = any; + +export class Lifecycle { + public readonly beforeTests = new LifecyclePhase<[]>({ + singular: true, + }); + public readonly beforeTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly beforeEachTest = new LifecyclePhase<[ItsATest]>(); + public readonly afterTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly testFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly testHookFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly cleanup = new LifecyclePhase<[]>({ + singular: true, + }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.ts new file mode 100644 index 00000000000000..22b73634543612 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_event.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 * as Rx from 'rxjs'; + +export type GetArgsType> = T extends LifecycleEvent + ? X + : never; + +export class LifecycleEvent { + private readonly handlers: Array<(...args: Args) => Promise | void> = []; + + private readonly beforeSubj = this.options.singular + ? new Rx.BehaviorSubject(undefined) + : new Rx.Subject(); + public readonly before$ = this.beforeSubj.asObservable(); + + private readonly afterSubj = this.options.singular + ? new Rx.BehaviorSubject(undefined) + : new Rx.Subject(); + public readonly after$ = this.afterSubj.asObservable(); + + constructor( + private readonly options: { + singular?: boolean; + } = {} + ) {} + + public add(fn: (...args: Args) => Promise | void) { + this.handlers.push(fn); + } + + public async trigger(...args: Args) { + if (this.beforeSubj.isStopped) { + throw new Error(`singular lifecycle event can only be triggered once`); + } + + this.beforeSubj.next(undefined); + if (this.options.singular) { + this.beforeSubj.complete(); + } + + try { + await Promise.all(this.handlers.map(async fn => await fn(...args))); + } finally { + this.afterSubj.next(undefined); + if (this.options.singular) { + this.afterSubj.complete(); + } + } + } +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts new file mode 100644 index 00000000000000..94dd76884f2cab --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts @@ -0,0 +1,206 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 Rx from 'rxjs'; +import { materialize, toArray } from 'rxjs/operators'; + +import { LifecyclePhase } from './lifecycle_phase'; + +describe('with randomness', () => { + beforeEach(() => { + const randomOrder = [0, 0.75, 0.5, 0.25, 1]; + jest.spyOn(Math, 'random').mockImplementation(() => { + const n = randomOrder.shift()!; + randomOrder.push(n); + return n; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('calls handlers in random order', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + phase.add( + jest.fn(() => { + order.push('one'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('two'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('three'); + }) + ); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "one", + "three", + "two", + ] + `); + }); +}); + +describe('without randomness', () => { + beforeEach(() => jest.spyOn(Math, 'random').mockImplementation(() => 0)); + afterEach(() => jest.restoreAllMocks()); + + it('calls all handlers and throws first error', async () => { + const phase = new LifecyclePhase(); + const fn1 = jest.fn(); + phase.add(fn1); + + const fn2 = jest.fn(() => { + throw new Error('foo'); + }); + phase.add(fn2); + + const fn3 = jest.fn(); + phase.add(fn3); + + await expect(phase.trigger()).rejects.toThrowErrorMatchingInlineSnapshot(`"foo"`); + expect(fn1).toHaveBeenCalled(); + expect(fn2).toHaveBeenCalled(); + expect(fn3).toHaveBeenCalled(); + }); + + it('triggers before$ just before calling handler and after$ once it resolves', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + const beforeSub = jest.fn(() => order.push('before')); + phase.before$.subscribe(beforeSub); + + const afterSub = jest.fn(() => order.push('after')); + phase.after$.subscribe(afterSub); + + const handler = jest.fn(async () => { + order.push('handler start'); + await new Promise(resolve => setTimeout(resolve, 100)); + order.push('handler done'); + }); + phase.add(handler); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "before", + "handler start", + "handler done", + "after", + ] + `); + }); + + it('completes before$ and after$ if phase is singular', async () => { + const phase = new LifecyclePhase({ singular: true }); + + const beforeNotifs: Array> = []; + phase.before$.pipe(materialize()).subscribe(n => beforeNotifs.push(n)); + + const afterNotifs: Array> = []; + phase.after$.pipe(materialize()).subscribe(n => afterNotifs.push(n)); + + await phase.trigger(); + expect(beforeNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + expect(afterNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('completes before$ subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.before$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('replays after$ event subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.after$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); +}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts new file mode 100644 index 00000000000000..5c7fdb532faa1f --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts @@ -0,0 +1,82 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 Rx from 'rxjs'; + +const shuffle = (arr: T[]) => arr.slice().sort(() => (Math.random() > 0.5 ? 1 : -1)); + +export type GetArgsType> = T extends LifecyclePhase + ? X + : never; + +export class LifecyclePhase { + private readonly handlers: Array<(...args: Args) => Promise | void> = []; + + private readonly beforeSubj = new Rx.Subject(); + public readonly before$ = this.beforeSubj.asObservable(); + + private readonly afterSubj = this.options.singular + ? new Rx.ReplaySubject(1) + : new Rx.Subject(); + public readonly after$ = this.afterSubj.asObservable(); + + constructor( + private readonly options: { + singular?: boolean; + } = {} + ) {} + + public add(fn: (...args: Args) => Promise | void) { + this.handlers.push(fn); + } + + public async trigger(...args: Args) { + if (this.beforeSubj.isStopped) { + throw new Error(`singular lifecycle event can only be triggered once`); + } + + this.beforeSubj.next(undefined); + if (this.options.singular) { + this.beforeSubj.complete(); + } + + // catch the first error but still execute all handlers + let error; + + // shuffle the handlers to prevent relying on their order + for (const fn of shuffle(this.handlers)) { + try { + await fn(...args); + } catch (_error) { + if (!error) { + error = _error; + } + } + } + + this.afterSubj.next(undefined); + if (this.options.singular) { + this.afterSubj.complete(); + } + + if (error) { + throw error; + } + } +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index e65eb2f27d0636..4eb45229c22346 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -58,7 +58,7 @@ export function decorateMochaUi(lifecycle, context) { argumentsList[1] = function() { before(async () => { - await lifecycle.trigger('beforeTestSuite', this); + await lifecycle.beforeTestSuite.trigger(this); }); this.tags = tags => { @@ -68,7 +68,7 @@ export function decorateMochaUi(lifecycle, context) { provider.call(this); after(async () => { - await lifecycle.trigger('afterTestSuite', this); + await lifecycle.afterTestSuite.trigger(this); }); }; @@ -94,7 +94,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testFailure', err, test); + await lifecycle.testFailure.trigger(err, test); }) ); } @@ -112,7 +112,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testHookFailure', err, test); + await lifecycle.testHookFailure.trigger(err, test); }) ); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js index ea697b096ce99f..0e8c1bc121e155 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js @@ -32,6 +32,7 @@ import { writeEpilogue } from './write_epilogue'; export function MochaReporterProvider({ getService }) { const log = getService('log'); const config = getService('config'); + const failureMetadata = getService('failureMetadata'); let originalLogWriters; let reporterCaptureStartTime; @@ -53,6 +54,7 @@ export function MochaReporterProvider({ getService }) { if (config.get('junit.enabled') && config.get('junit.reportName')) { setupJUnitReportGeneration(runner, { reportName: config.get('junit.reportName'), + getTestMetadata: t => failureMetadata.get(t), }); } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts index 2d98052b1062a4..654f588fda8580 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts @@ -35,7 +35,7 @@ export async function runTests(lifecycle: Lifecycle, mocha: Mocha) { runComplete = true; }); - lifecycle.on('cleanup', () => { + lifecycle.cleanup.add(() => { if (!runComplete) runner.abort(); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index a425251a29f36f..326877919d9855 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -41,7 +41,7 @@ export async function setupMocha(lifecycle, log, config, providers) { // global beforeEach hook in root suite triggers before all others mocha.suite.beforeEach('global before each', async function() { - await lifecycle.trigger('beforeEachTest', this.currentTest); + await lifecycle.beforeEachTest.trigger(this.currentTest); }); loadTestFiles({ diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 62739fd37030f1..06a83cdd8b1dcb 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -49,3 +49,5 @@ export { } from './mocha'; export { runFailedTestsReporterCli } from './failed_tests_reporter'; + +export { makeJunitReportPath } from './junit_report_path'; diff --git a/packages/kbn-test/src/junit_report_path.ts b/packages/kbn-test/src/junit_report_path.ts new file mode 100644 index 00000000000000..11eaf3d2b14a5e --- /dev/null +++ b/packages/kbn-test/src/junit_report_path.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 { resolve } from 'path'; + +const job = process.env.JOB ? `job-${process.env.JOB}-` : ''; +const num = process.env.CI_WORKER_NUMBER ? `worker-${process.env.CI_WORKER_NUMBER}-` : ''; + +export function makeJunitReportPath(rootDirectory: string, reportName: string) { + return resolve( + rootDirectory, + 'target/junit', + process.env.JOB || '.', + `TEST-${job}${num}${reportName}.xml` + ); +} diff --git a/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js b/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js index 1cb53ea0ca1c5e..7472e271bd1e9c 100644 --- a/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/__tests__/junit_report_generation.js @@ -25,6 +25,7 @@ import { parseString } from 'xml2js'; import del from 'del'; import Mocha from 'mocha'; import expect from '@kbn/expect'; +import { makeJunitReportPath } from '@kbn/test'; import { setupJUnitReportGeneration } from '../junit_report_generation'; @@ -50,17 +51,7 @@ describe('dev/mocha/junit report generation', () => { mocha.addFile(resolve(PROJECT_DIR, 'test.js')); await new Promise(resolve => mocha.run(resolve)); const report = await fcb(cb => - parseString( - readFileSync( - resolve( - PROJECT_DIR, - 'target/junit', - process.env.JOB || '.', - `TEST-${process.env.JOB ? process.env.JOB + '-' : ''}test.xml` - ) - ), - cb - ) + parseString(readFileSync(makeJunitReportPath(PROJECT_DIR, 'test')), cb) ); // test case results are wrapped in @@ -98,6 +89,7 @@ describe('dev/mocha/junit report generation', () => { classname: sharedClassname, name: 'SUITE works', time: testPass.$.time, + 'metadata-json': '{}', }, 'system-out': testPass['system-out'], }); @@ -109,6 +101,7 @@ describe('dev/mocha/junit report generation', () => { classname: sharedClassname, name: 'SUITE fails', time: testFail.$.time, + 'metadata-json': '{}', }, 'system-out': testFail['system-out'], failure: [testFail.failure[0]], @@ -124,6 +117,7 @@ describe('dev/mocha/junit report generation', () => { classname: sharedClassname, name: 'SUITE SUB_SUITE "before each" hook: fail hook for "never runs"', time: beforeEachFail.$.time, + 'metadata-json': '{}', }, 'system-out': testFail['system-out'], failure: [beforeEachFail.failure[0]], @@ -133,6 +127,7 @@ describe('dev/mocha/junit report generation', () => { $: { classname: sharedClassname, name: 'SUITE SUB_SUITE never runs', + 'metadata-json': '{}', }, 'system-out': testFail['system-out'], skipped: [''], diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 51601fab12e531..95e84117106a4d 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -17,11 +17,12 @@ * under the License. */ -import { resolve, dirname, relative } from 'path'; +import { dirname, relative } from 'path'; import { writeFileSync, mkdirSync } from 'fs'; import { inspect } from 'util'; import xmlBuilder from 'xmlbuilder'; +import { makeJunitReportPath } from '@kbn/test'; import { getSnapshotOfRunnableLogs } from './log_cache'; import { escapeCdata } from '../'; @@ -32,6 +33,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { const { reportName = 'Unnamed Mocha Tests', rootDirectory = dirname(require.resolve('../../../../package.json')), + getTestMetadata = () => ({}), } = options; const stats = {}; @@ -118,6 +120,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { name: getFullTitle(node), classname: `${reportName}.${getPath(node).replace(/\./g, '·')}`, time: getDuration(node), + 'metadata-json': JSON.stringify(getTestMetadata(node) || {}), }); } @@ -135,13 +138,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { } }); - const reportPath = resolve( - rootDirectory, - 'target/junit', - process.env.JOB || '.', - `TEST-${process.env.JOB ? process.env.JOB + '-' : ''}${reportName}.xml` - ); - + const reportPath = makeJunitReportPath(rootDirectory, reportName); const reportXML = builder.end(); mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); diff --git a/packages/kbn-test/types/ftr.d.ts b/packages/kbn-test/types/ftr.d.ts index e917ed63ca5d30..8beecab88878da 100644 --- a/packages/kbn-test/types/ftr.d.ts +++ b/packages/kbn-test/types/ftr.d.ts @@ -18,9 +18,9 @@ */ import { ToolingLog } from '@kbn/dev-utils'; -import { Config, Lifecycle } from '../src/functional_test_runner/lib'; +import { Config, Lifecycle, FailureMetadata } from '../src/functional_test_runner/lib'; -export { Lifecycle, Config }; +export { Lifecycle, Config, FailureMetadata }; interface AsyncInstance { /** @@ -61,7 +61,7 @@ export interface GenericFtrProviderContext< * Determine if a service is avaliable * @param serviceName */ - hasService(serviceName: 'config' | 'log' | 'lifecycle'): true; + hasService(serviceName: 'config' | 'log' | 'lifecycle' | 'failureMetadata'): true; hasService(serviceName: K): serviceName is K; hasService(serviceName: string): serviceName is Extract; @@ -73,6 +73,7 @@ export interface GenericFtrProviderContext< getService(serviceName: 'config'): Config; getService(serviceName: 'log'): ToolingLog; getService(serviceName: 'lifecycle'): Lifecycle; + getService(serviceName: 'failureMetadata'): FailureMetadata; getService(serviceName: T): ServiceMap[T]; /** diff --git a/packages/kbn-ui-framework/dist/kui_dark.css b/packages/kbn-ui-framework/dist/kui_dark.css index dcbd65fbca520b..aa16bcdb34d368 100644 --- a/packages/kbn-ui-framework/dist/kui_dark.css +++ b/packages/kbn-ui-framework/dist/kui_dark.css @@ -1,10 +1,24 @@ +/* 1 */ +/* 1 */ +/** + * 1. Extend beta badges to at least 40% of the container's width + * 2. Fix for IE to ensure badges are visible outside of a - - - - { - editorInstanceRef.current!.getRequestsAsCURL(cb); - }} - getDocumentation={() => { - return getDocumentation(editorInstanceRef.current!, docLinkVersion); - }} - autoIndent={(event: any) => { - autoIndent(editorInstanceRef.current!, event); - }} - addNotification={({ title }) => notifications.toasts.add({ title })} - /> - - -
-
- - ); -} - -export const Editor = React.memo(EditorUI); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx deleted file mode 100644 index c167155bd18a91..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ /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 React, { useEffect, useRef } from 'react'; -import $ from 'jquery'; - -// @ts-ignore -import { initializeOutput } from '../../../../../../../public/quarantined/src/output'; -import { - useServicesContext, - useEditorReadContext, - useRequestReadContext, -} from '../../../../contexts'; - -// @ts-ignore -import utils from '../../../../../../../public/quarantined/src/utils'; - -import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; -import { applyCurrentSettings } from './apply_editor_settings'; - -function modeForContentType(contentType: string) { - if (contentType.indexOf('application/json') >= 0) { - return 'ace/mode/json'; - } else if (contentType.indexOf('application/yaml') >= 0) { - return 'ace/mode/yaml'; - } - return 'ace/mode/text'; -} - -function EditorOutputUI() { - const editorRef = useRef(null); - const editorInstanceRef = useRef(null); - const { services } = useServicesContext(); - - const { settings: readOnlySettings } = useEditorReadContext(); - const { - lastResult: { data, error }, - } = useRequestReadContext(); - - useEffect(() => { - const editor$ = $(editorRef.current!); - editorInstanceRef.current = initializeOutput(editor$, services.settings); - const unsubscribe = subscribeResizeChecker(editorRef.current!, editorInstanceRef.current); - - return () => { - unsubscribe(); - }; - }, [services.settings]); - - useEffect(() => { - if (data) { - const mode = modeForContentType(data[0].response.contentType); - editorInstanceRef.current.session.setMode(mode); - editorInstanceRef.current.update( - data - .map(d => d.response.value) - .map(readOnlySettings.tripleQuotes ? utils.expandLiteralStrings : a => a) - .join('\n') - ); - } else if (error) { - editorInstanceRef.current.session.setMode(modeForContentType(error.contentType)); - editorInstanceRef.current.update(error.value); - } else { - editorInstanceRef.current.update(''); - } - }, [readOnlySettings, data, error]); - - useEffect(() => { - applyCurrentSettings(editorInstanceRef.current, readOnlySettings); - }, [readOnlySettings]); - - return ( -
-
-
- ); -} - -export const EditorOutput = React.memo(EditorOutputUI); 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 deleted file mode 100644 index d1605c1aefa566..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.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 { throttle } from 'lodash'; - -interface Actions { - input: any; // TODO: Wrap this in an editor interface - sendCurrentRequestToES: () => void; - openDocumentation: () => void; -} - -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' }, - exec: () => sendCurrentRequestToES(), - }); - input.commands.addCommand({ - name: 'open documentation', - bindKey: { win: 'Ctrl-/', mac: 'Command-/' }, - exec: () => { - openDocumentation(); - }, - }); - input.commands.addCommand({ - name: 'auto indent request', - bindKey: { win: 'Ctrl-I', mac: 'Command-I' }, - exec: () => { - throttledAutoIndent(); - }, - }); - input.commands.addCommand({ - name: 'move to previous request start or end', - bindKey: { win: 'Ctrl-Up', mac: 'Command-Up' }, - exec: () => { - input.moveToPreviousRequestEdge(); - }, - }); - input.commands.addCommand({ - name: 'move to next request start or end', - bindKey: { win: 'Ctrl-Down', mac: 'Command-Down' }, - exec: () => { - input.moveToNextRequestEdge(); - }, - }); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts deleted file mode 100644 index 0eb6a8fec4f86e..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts +++ /dev/null @@ -1,96 +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 sinon from 'sinon'; -import $ from 'jquery'; - -import { loadRemoteState } from './load_remote_editor_state'; - -describe('[Legacy Console] loading remote editor state', () => { - const sandbox = sinon.createSandbox(); - - let inputMock: any; - let ajaxDoneStub: any; - beforeEach(() => { - ajaxDoneStub = sinon.stub(); - sandbox.stub($, 'ajax').returns({ done: ajaxDoneStub } as any); - - inputMock = { - update: sinon.stub(), - moveToNextRequestEdge: sinon.stub(), - highlightCurrentRequestsAndUpdateActionBar: sinon.stub(), - updateActionsBar: sinon.stub(), - getSession: sinon.stub().returns({ on() {} }), - }; - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('correctly loads state from any external HTTPS links.', () => { - const mockContent = {}; - ajaxDoneStub.yields(mockContent); - - loadRemoteState({ - input: inputMock, - url: 'https://state.link.com/content', - }); - - sinon.assert.calledOnce($.ajax as any); - sinon.assert.calledWithExactly($.ajax as any, { - url: 'https://state.link.com/content', - dataType: 'text', - kbnXsrfToken: false, - headers: {}, - }); - - sinon.assert.calledTwice(inputMock.moveToNextRequestEdge); - sinon.assert.calledWithExactly(inputMock.moveToNextRequestEdge, true); - sinon.assert.calledOnce(inputMock.highlightCurrentRequestsAndUpdateActionBar); - sinon.assert.calledOnce(inputMock.updateActionsBar); - sinon.assert.calledOnce(inputMock.update); - sinon.assert.calledWithExactly(inputMock.update, sinon.match.same(mockContent)); - }); - - it('correctly loads state from GitHub API HTTPS links.', () => { - const mockContent = {}; - ajaxDoneStub.yields(mockContent); - - loadRemoteState({ - input: inputMock, - url: 'https://api.github.com/content', - }); - - sinon.assert.calledOnce($.ajax as any); - sinon.assert.calledWithExactly($.ajax as any, { - url: 'https://api.github.com/content', - dataType: 'text', - kbnXsrfToken: false, - headers: { Accept: 'application/vnd.github.v3.raw' }, - }); - - sinon.assert.calledTwice(inputMock.moveToNextRequestEdge); - sinon.assert.calledWithExactly(inputMock.moveToNextRequestEdge, true); - sinon.assert.calledOnce(inputMock.highlightCurrentRequestsAndUpdateActionBar); - sinon.assert.calledOnce(inputMock.updateActionsBar); - sinon.assert.calledOnce(inputMock.update); - sinon.assert.calledWithExactly(inputMock.update, sinon.match.same(mockContent)); - }); -}); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.ts deleted file mode 100644 index b2f475036c5da3..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.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 $ from 'jquery'; - -// @ts-ignore -// import mappings from '../../../../../../public/quarantined/src/mappings'; - -/* eslint-disable no-console */ - -export interface InitializationArgs { - url: string; - input: any; -} - -export function loadRemoteState({ url, input }: InitializationArgs) { - const loadFrom = { - url, - // Having dataType here is required as it doesn't allow jQuery to `eval` content - // coming from the external source thereby preventing XSS attack. - dataType: 'text', - kbnXsrfToken: false, - headers: {}, - }; - - if (/https?:\/\/api.github.com/.test(url)) { - loadFrom.headers = { Accept: 'application/vnd.github.v3.raw' }; - } - - $.ajax(loadFrom).done(data => { - input.update(data); - input.moveToNextRequestEdge(true); - input.highlightCurrentRequestsAndUpdateActionBar(); - input.updateActionsBar(); - }); - input.moveToNextRequestEdge(true); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.ts deleted file mode 100644 index b728df615fc9dd..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.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. - */ - -// @ts-ignore -import { getEndpointFromPosition } from '../../../../../../public/quarantined/src/autocomplete'; - -export function autoIndent(editor: any, event: any) { - editor.autoIndent(); - event.preventDefault(); - editor.focus(); -} - -export function getDocumentation(editor: any, docLinkVersion: string): Promise { - return new Promise(resolve => { - editor.getRequestsInRange((requests: any) => { - if (!requests || requests.length === 0) { - resolve(null); - return; - } - const position = requests[0].range.end; - position.column = position.column - 1; - const endpoint = getEndpointFromPosition(editor, position, editor.parser); - if (endpoint && endpoint.documentation && endpoint.documentation.indexOf('http') !== -1) { - const nextDocumentation = endpoint.documentation - .replace('/master/', `/${docLinkVersion}/`) - .replace('/current/', `/${docLinkVersion}/`); - resolve(nextDocumentation); - } else { - resolve(null); - } - }); - }); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/settings.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/settings.tsx deleted file mode 100644 index 8440faa6eeea82..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/settings.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; - -import { AutocompleteOptions, DevToolsSettingsModal } from '../components'; - -// @ts-ignore -import mappings from '../../../../public/quarantined/src/mappings'; -import { useServicesContext, useEditorActionContext } from '../contexts'; -import { DevToolsSettings } from '../../services'; - -const getAutocompleteDiff = (newSettings: DevToolsSettings, prevSettings: DevToolsSettings) => { - return Object.keys(newSettings.autocomplete).filter(key => { - // @ts-ignore - return prevSettings.autocomplete[key] !== newSettings.autocomplete[key]; - }); -}; - -const refreshAutocompleteSettings = (selectedSettings: any) => { - mappings.retrieveAutoCompleteInfo(selectedSettings); -}; - -const fetchAutocompleteSettingsIfNeeded = ( - newSettings: DevToolsSettings, - prevSettings: DevToolsSettings -) => { - // We'll only retrieve settings if polling is on. The expectation here is that if the user - // disables polling it's because they want manual control over the fetch request (possibly - // because it's a very expensive request given their cluster and bandwidth). In that case, - // they would be unhappy with any request that's sent automatically. - if (newSettings.polling) { - const autocompleteDiff = getAutocompleteDiff(newSettings, prevSettings); - - const isSettingsChanged = autocompleteDiff.length > 0; - const isPollingChanged = prevSettings.polling !== newSettings.polling; - - if (isSettingsChanged) { - // If the user has changed one of the autocomplete settings, then we'll fetch just the - // ones which have changed. - const changedSettings: any = autocompleteDiff.reduce( - (changedSettingsAccum: any, setting: string): any => { - changedSettingsAccum[setting] = newSettings.autocomplete[setting as AutocompleteOptions]; - return changedSettingsAccum; - }, - {} - ); - mappings.retrieveAutoCompleteInfo(changedSettings.autocomplete); - } else if (isPollingChanged) { - // If the user has turned polling on, then we'll fetch all selected autocomplete settings. - mappings.retrieveAutoCompleteInfo(); - } - } -}; - -export interface Props { - onClose: () => void; -} - -export function Settings({ onClose }: Props) { - const { - services: { settings }, - } = useServicesContext(); - - const dispatch = useEditorActionContext(); - - const onSaveSettings = (newSettings: DevToolsSettings) => { - const prevSettings = settings.toJSON(); - fetchAutocompleteSettingsIfNeeded(newSettings, prevSettings); - - // Update the new settings in localStorage - settings.updateSettings(newSettings); - - // Let the rest of the application know settings have updated. - dispatch({ - type: 'updateSettings', - payload: newSettings, - }); - onClose(); - }; - - return ( - - ); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts b/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts deleted file mode 100644 index b053e605b5faef..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts +++ /dev/null @@ -1,54 +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. - */ - -/** - * This function is considered legacy and should not be changed or updated before we have editor - * interfaces in place (it's using a customized version of Ace directly). - */ -export function restoreRequestFromHistory(input: any, req: any) { - const session = input.getSession(); - let pos = input.getCursorPosition(); - let prefix = ''; - let suffix = '\n'; - if (input.parser.isStartRequestRow(pos.row)) { - pos.column = 0; - suffix += '\n'; - } else if (input.parser.isEndRequestRow(pos.row)) { - const line = session.getLine(pos.row); - pos.column = line.length; - prefix = '\n\n'; - } else if (input.parser.isInBetweenRequestsRow(pos.row)) { - pos.column = 0; - } else { - pos = input.nextRequestEnd(pos); - prefix = '\n\n'; - } - - let s = prefix + req.method + ' ' + req.endpoint; - if (req.data) { - s += '\n' + req.data; - } - - s += suffix; - - session.insert(pos, s); - input.clearSelection(); - input.moveCursorTo(pos.row + prefix.length, 0); - input.focus(); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx b/src/legacy/core_plugins/console/np_ready/public/application/index.tsx deleted file mode 100644 index e181caf23d2cba..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { NotificationsSetup } from 'src/core/public'; -import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts'; -import { Main } from './containers'; -import { createStorage, createHistory, createSettings, Settings } from '../services'; - -let settingsRef: Settings; -export function legacyBackDoorToSettings() { - return settingsRef; -} - -export function boot(deps: { - docLinkVersion: string; - I18nContext: any; - notifications: NotificationsSetup; -}) { - const { I18nContext, notifications, docLinkVersion } = deps; - - const storage = createStorage({ - engine: window.localStorage, - prefix: 'sense:', - }); - const history = createHistory({ storage }); - const settings = createSettings({ storage }); - settingsRef = settings; - - return ( - - - - -
- - - - - ); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/index.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/index.ts deleted file mode 100644 index c4ac6f1454b7fe..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/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 './legacy_editor'; 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 deleted file mode 100644 index 0dc1bcc96ddee2..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.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 { 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 deleted file mode 100644 index f8c3f425a10320..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts +++ /dev/null @@ -1,110 +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 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) {} - - getLineState(lineNumber: number) { - const session = this.editor.getSession(); - return session.getState(lineNumber - 1); - } - - getValueInRange({ start, end }: Range): string { - const session = this.editor.getSession(); - const aceRange = new _AceRange( - start.lineNumber - 1, - start.column - 1, - end.lineNumber - 1, - end.column - 1 - ); - return session.doc.getTextRange(aceRange); - } - - getTokenProvider(): TokensProvider { - return new AceTokensProvider(this.editor.getSession()); - } - - getValue(): string { - return this.editor.getValue(); - } - - getLineValue(lineNumber: number): string { - const session = this.editor.getSession(); - return session.getLine(lineNumber - 1); - } - - getCurrentPosition(): Position { - const cursorPosition = this.editor.getCursorPosition(); - return { - lineNumber: cursorPosition.row + 1, - column: cursorPosition.column + 1, - }; - } - - clearSelection(): void { - this.editor.clearSelection(); - } - - getTokenAt(pos: Position): Token | null { - const provider = this.getTokenProvider(); - return provider.getTokenAt(pos); - } - - insert(valueOrPos: string | Position, value?: string): void { - if (typeof valueOrPos === 'string') { - this.editor.insert(valueOrPos); - return; - } - const document = this.editor.getSession().getDocument(); - document.insert( - { - column: valueOrPos.column - 1, - row: valueOrPos.lineNumber - 1, - }, - value || '' - ); - } - - moveCursorToPosition(pos: Position): void { - this.editor.moveCursorToPosition({ row: pos.lineNumber - 1, column: pos.column - 1 }); - } - - replace({ start, end }: Range, value: string): void { - const aceRange = new _AceRange( - start.lineNumber - 1, - start.column - 1, - end.lineNumber - 1, - end.column - 1 - ); - const session = this.editor.getSession(); - session.replace(aceRange, value); - } - - getLines(startLine: number, endLine: number): string[] { - const session = this.editor.getSession(); - return session.getLines(startLine - 1, endLine - 1); - } -} diff --git a/src/legacy/core_plugins/console/np_ready/public/index.ts b/src/legacy/core_plugins/console/np_ready/public/index.ts deleted file mode 100644 index 3f8d162f62d44c..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/index.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. - */ - -import { PluginInitializerContext } from '../../../../../core/public'; - -import { ConsoleUIPlugin } from './plugin'; - -export { ConsoleUIPlugin as Plugin }; - -export function plugin(ctx: PluginInitializerContext) { - return new ConsoleUIPlugin(ctx); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts deleted file mode 100644 index 758ea81be88ad6..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.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 'brace'; -import 'brace/ext/language_tools'; -import 'brace/ext/searchbox'; -import 'brace/mode/json'; -import 'brace/mode/text'; - -/* eslint-disable @kbn/eslint/no-restricted-paths */ -import { npSetup, npStart } from 'ui/new_platform'; -import { I18nContext } from 'ui/i18n'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ - -export interface XPluginSet { - dev_tools: DevToolsSetup; - home: HomePublicPluginSetup; - __LEGACY: { - I18nContext: any; - }; -} - -import { plugin } from '.'; -import { DevToolsSetup } from '../../../../../plugins/dev_tools/public'; -import { HomePublicPluginSetup } from '../../../../../plugins/home/public'; - -const pluginInstance = plugin({} as any); - -pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - I18nContext, - }, -}); -pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/console/np_ready/public/plugin.ts b/src/legacy/core_plugins/console/np_ready/public/plugin.ts deleted file mode 100644 index 37758adc98d117..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/plugin.ts +++ /dev/null @@ -1,77 +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 { render, unmountComponentAtNode } from 'react-dom'; -import { i18n } from '@kbn/i18n'; - -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from '../../../../../core/public'; -import { XPluginSet } from './legacy'; - -export class ConsoleUIPlugin implements Plugin { - // @ts-ignore - constructor(private readonly ctx: PluginInitializerContext) {} - - async setup({ notifications }: CoreSetup, pluginSet: XPluginSet) { - const { - __LEGACY: { I18nContext }, - dev_tools, - home, - } = pluginSet; - - home.featureCatalogue.register({ - id: 'console', - title: i18n.translate('console.devToolsTitle', { - defaultMessage: 'Console', - }), - description: i18n.translate('console.devToolsDescription', { - defaultMessage: 'Skip cURL and use this JSON interface to work with your data directly.', - }), - icon: 'consoleApp', - path: '/app/kibana#/dev_tools/console', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }); - - dev_tools.register({ - id: 'console', - order: 1, - title: i18n.translate('console.consoleDisplayName', { - defaultMessage: 'Console', - }), - enableRouting: false, - async mount(ctx, { element }) { - const { boot } = await import('./application'); - render( - boot({ - docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, - I18nContext, - notifications, - }), - element - ); - return () => { - unmountComponentAtNode(element); - }; - }, - }); - } - - async start(core: CoreStart) {} -} diff --git a/src/legacy/core_plugins/console/np_ready/public/types/core_editor.ts b/src/legacy/core_plugins/console/np_ready/public/types/core_editor.ts deleted file mode 100644 index fdd2e2639e554b..00000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/types/core_editor.ts +++ /dev/null @@ -1,155 +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 { TokensProvider } from './tokens_provider'; -import { Token } from './token'; - -export interface Position { - /** - * The line number, not zero-indexed. - * - * E.g., if given line number 1, this would refer to the first line visible. - */ - lineNumber: number; - - /** - * The column number, not zero-indexed. - * - * E.g., if given column number 1, this would refer to the first character of a column. - */ - column: number; -} - -export interface Range { - /** - * The start point of the range. - */ - start: Position; - - /** - * The end point of the range. - */ - end: Position; -} - -/** - * Enumeration of the different states the current position can be in. - * - * Current implementation uses low-level binary operations OR ('|') and AND ('&') to, respectively: - * - * - Create a combination of acceptable states. - * - Extract the states from the acceptable combination. - * - * E.g. - * ```ts - * const acceptableStates = LINE_MODE.REQUEST_START | LINE_MODE.IN_REQUEST; // binary '110' - * - * // Is MULTI_DOC_CUR_DOC_END ('1000') acceptable? - * Boolean(acceptableStates & LINE_MODE.MULTI_DOC_CUR_DOC_END) // false - * - * // Is REQUEST_START ('10') acceptable? - * Boolean(acceptableStates & LINE_MODE.REQUEST_START) // true - * ``` - * - * This implementation will probably be changed to something more accessible in future but is documented - * here for reference. - */ -export enum LINE_MODE { - REQUEST_START = 2, - IN_REQUEST = 4, - MULTI_DOC_CUR_DOC_END = 8, - REQUEST_END = 16, - BETWEEN_REQUESTS = 32, - UNKNOWN = 64, -} - -/** - * The CoreEditor is a component separate from the Editor implementation that provides Console - * app specific business logic. The CoreEditor is an interface to the lower-level editor implementation - * being used which is usually vendor code such as Ace or Monaco. - */ -export interface CoreEditor { - /** - * Get the current position of the cursor. - */ - getCurrentPosition(): Position; - - /** - * Get the contents of the editor. - */ - getValue(): string; - - /** - * Get the contents of the editor at a specific line. - */ - getLineValue(lineNumber: number): string; - - /** - * Insert a string value at the current cursor position. - */ - insert(value: string): void; - - /** - * Insert a string value at the indicated position. - */ - insert(pos: Position, value: string): void; - - /** - * Replace a range of text. - */ - replace(rangeToReplace: Range, value: string): void; - - /** - * Clear the selected range. - */ - clearSelection(): void; - - /** - * Move the cursor to the indicated position. - */ - moveCursorToPosition(pos: Position): void; - - /** - * Get the token at the indicated position. The token considered "at" the position is the - * one directly preceding the position. - * - * Returns null if there is no such token. - */ - getTokenAt(pos: Position): Token | null; - - /** - * Get an iterable token provider. - */ - getTokenProvider(): TokensProvider; - - /** - * Get the contents of the editor between two points. - */ - getValueInRange(range: Range): string; - - /** - * Get the lexer state at the end of a specific line. - */ - getLineState(lineNumber: number): string; - - /** - * Get line content between and including the start and end lines provided. - */ - getLines(startLine: number, endLine: number): string[]; -} diff --git a/src/legacy/core_plugins/console/np_ready/server/.gitkeep b/src/legacy/core_plugins/console/np_ready/server/.gitkeep deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/src/legacy/core_plugins/console/public/README.md b/src/legacy/core_plugins/console/public/README.md deleted file mode 100644 index 3a53c46143793d..00000000000000 --- a/src/legacy/core_plugins/console/public/README.md +++ /dev/null @@ -1,21 +0,0 @@ -## New Platform (NP) Ready vs Quarantined - -We want to move toward more modularised code in the Console app. -Part of the effort means separating out different console components -like: - -- The language parser -- The editor rendering component -- Autocomplete -- The UI container components - -In addition to this effort we want to bring Console in line with NP -requirements and ensure that we are not using angular and public ui -in this app anymore. - -The quarantined folder contains all of the code that has not been cleared -for living in the new platform as it has not been properly refactored -or has dependencies on, for example, UI public. - -Over time, the quarantined part of the code should shrink to nothing -and we should only have NP ready code. diff --git a/src/legacy/core_plugins/console/public/kibana.json b/src/legacy/core_plugins/console/public/kibana.json new file mode 100644 index 00000000000000..3363af353912ac --- /dev/null +++ b/src/legacy/core_plugins/console/public/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "console", + "version": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["home"] +} diff --git a/src/legacy/core_plugins/console/public/legacy.ts b/src/legacy/core_plugins/console/public/legacy.ts new file mode 100644 index 00000000000000..c456d777187aad --- /dev/null +++ b/src/legacy/core_plugins/console/public/legacy.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { npSetup, npStart } from 'ui/new_platform'; +import { I18nContext } from 'ui/i18n'; +import chrome from 'ui/chrome'; +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; + +export interface XPluginSet { + dev_tools: DevToolsSetup; + home: HomePublicPluginSetup; + __LEGACY: { + I18nContext: any; + elasticsearchUrl: string; + category: FeatureCatalogueCategory; + }; +} + +import { plugin } from './np_ready'; +import { DevToolsSetup } from '../../../../plugins/dev_tools/public'; +import { HomePublicPluginSetup } from '../../../../plugins/home/public'; + +const pluginInstance = plugin({} as any); + +(async () => { + await pluginInstance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + elasticsearchUrl: chrome.getInjected('elasticsearchUrl'), + I18nContext, + category: FeatureCatalogueCategory.ADMIN, + }, + }); + await pluginInstance.start(npStart.core); +})(); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/console_menu.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/console_menu.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/components/console_menu.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/console_menu.tsx index fd87c0b94ef5b0..7842c15be267f4 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/components/console_menu.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/console_menu.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; interface Props { - getCurl: (cb: (text: string) => void) => void; + getCurl: () => Promise; getDocumentation: () => Promise; autoIndent: (ev?: React.MouseEvent) => void; addNotification?: (opts: { title: string }) => void; @@ -48,7 +48,7 @@ export class ConsoleMenu extends Component { mouseEnter = () => { if (this.state.isPopoverOpen) return; - this.props.getCurl(text => { + this.props.getCurl().then(text => { this.setState({ curlCode: text }); }); }; diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/editor_example.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_example.tsx similarity index 80% rename from src/legacy/core_plugins/console/np_ready/public/application/components/editor_example.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/editor_example.tsx index 01bd3fcd78e531..ea08a06a9e39ba 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/components/editor_example.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_example.tsx @@ -20,9 +20,7 @@ import React, { useEffect } from 'react'; // @ts-ignore import exampleText from 'raw-loader!../constants/help_example.txt'; -import $ from 'jquery'; -// @ts-ignore -import SenseEditor from '../../../../public/quarantined/src/sense_editor/editor'; +import { createReadOnlyAceEditor } from '../models/legacy_core_editor'; interface EditorExampleProps { panel: string; @@ -32,11 +30,9 @@ export function EditorExample(props: EditorExampleProps) { const elemId = `help-example-${props.panel}`; useEffect(() => { - const el = $(`#${elemId}`); - el.text(exampleText.trim()); - const editor = new SenseEditor(el); - editor.setReadOnly(true); - editor.$blockScrolling = Infinity; + const el = document.querySelector(`#${elemId}`)!; + el.textContent = exampleText.trim(); + const editor = createReadOnlyAceEditor(el); return () => { editor.destroy(); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/help_panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/help_panel.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/help_panel.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/help_panel.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/components/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/settings_modal.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/settings_modal.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/settings_modal.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/settings_modal.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/components/resizer.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/components/resizer.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/components/resizer.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/components/resizer.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel_container.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel_container.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/context.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/context.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/context.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/registry.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/registry.ts rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/split_panel.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/split_panel.test.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx index e60912a29355ba..304535421a78a6 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/split_panel.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx @@ -22,7 +22,7 @@ import { mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import { spy } from 'sinon'; -import { PanelsContainer, Panel } from '.'; +import { PanelsContainer, Panel } from './index'; const testComponentA =

A

; const testComponentB =

B

; diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/top_nav_menu.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/top_nav_menu.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/welcome_panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/welcome_panel.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/welcome_panel.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/welcome_panel.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/constants/help_example.txt b/src/legacy/core_plugins/console/public/np_ready/application/constants/help_example.txt similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/constants/help_example.txt rename to src/legacy/core_plugins/console/public/np_ready/application/constants/help_example.txt diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/console_history.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/console_history.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/console_history.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/console_history.tsx index 30966a2f77e1da..8ec8b9c61bf03c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/console_history.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/console_history.tsx @@ -175,7 +175,7 @@ export function ConsoleHistory({ close }: Props) { role="option" onMouseEnter={() => setViewingReq(req)} onMouseLeave={() => setViewingReq(selectedReq.current)} - onDoubleClick={restoreRequestFromHistory} + onDoubleClick={() => restoreRequestFromHistory(selectedReq.current)} aria-label={i18n.translate('console.historyPage.itemOfRequestListAriaLabel', { defaultMessage: 'Request: {historyItem}', values: { historyItem: reqDescription }, diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/history_viewer.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/history_viewer.tsx similarity index 83% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/history_viewer.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/history_viewer.tsx index 6fbb46bba62129..2a5ffee149b92d 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/history_viewer.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/history_viewer.tsx @@ -19,13 +19,14 @@ import React, { useEffect, useRef } from 'react'; import { i18n } from '@kbn/i18n'; -import $ from 'jquery'; import { DevToolsSettings } from '../../../services'; import { subscribeResizeChecker } from '../editor/legacy/subscribe_console_resize_checker'; // @ts-ignore -import SenseEditor from '../../../../../public/quarantined/src/sense_editor/editor'; +import * as InputMode from '../../models/legacy_core_editor/mode/input'; +const inputMode = new InputMode.Mode(); +import * as editor from '../../models/legacy_core_editor'; import { applyCurrentSettings } from '../editor/legacy/console_editor/apply_editor_settings'; interface Props { @@ -35,13 +36,11 @@ interface Props { export function HistoryViewer({ settings, req }: Props) { const divRef = useRef(null); - const viewerRef = useRef(null); + const viewerRef = useRef(null); useEffect(() => { - const viewer = new SenseEditor($(divRef.current!)); + const viewer = editor.createReadOnlyAceEditor(divRef.current!); viewerRef.current = viewer; - viewer.renderer.setShowPrintMargin(false); - viewer.$blockScrolling = Infinity; const unsubscribe = subscribeResizeChecker(divRef.current!, viewer); return () => unsubscribe(); }, []); @@ -54,13 +53,14 @@ export function HistoryViewer({ settings, req }: Props) { const { current: viewer } = viewerRef; if (req) { const s = req.method + ' ' + req.endpoint + '\n' + (req.data || ''); - viewer.setValue(s); + viewer.update(s, inputMode); viewer.clearSelection(); } else { - viewer.getSession().setValue( + viewer.update( i18n.translate('console.historyPage.noHistoryTextMessage', { defaultMessage: 'No history available', - }) + }), + inputMode ); } } diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/editor.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/index.ts diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.ts new file mode 100644 index 00000000000000..f9cfadf7fe34b4 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.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 { DevToolsSettings } from '../../../../../services'; +import { CoreEditor } from '../../../../../types'; +import { CustomAceEditor } from '../../../../models/legacy_core_editor'; + +export function applyCurrentSettings( + editor: CoreEditor | CustomAceEditor, + settings: DevToolsSettings +) { + if ((editor as any).setStyles) { + (editor as CoreEditor).setStyles({ + wrapLines: settings.wrapMode, + fontSize: settings.fontSize + 'px', + }); + } else { + (editor as CustomAceEditor).getSession().setUseWrapMode(settings.wrapMode); + (editor as CustomAceEditor).container.style.fontSize = settings.fontSize + 'px'; + } +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx new file mode 100644 index 00000000000000..5df72c0f034966 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx @@ -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. + */ + +jest.mock('../../../../contexts/editor_context/editor_registry.ts', () => ({ + instance: { + setInputEditor: () => {}, + getInputEditor: () => ({ + getRequestsInRange: async () => [{ test: 'test' }], + }), + }, +})); +jest.mock('../../../../components/editor_example.tsx', () => {}); +jest.mock('../../../../../lib/mappings/mappings', () => ({ + retrieveAutoCompleteInfo: () => {}, + clearSubscriptions: () => {}, +})); +jest.mock('../../../../models/sense_editor', () => { + return { + create: () => ({ + getCoreEditor: () => ({ + registerKeyboardShortcut: jest.fn(), + setStyles: jest.fn(), + getContainer: () => ({ + focus: () => {}, + }), + on: jest.fn(), + }), + update: jest.fn(), + commands: { + addCommand: () => {}, + }, + }), + }; +}); + +jest.mock('../../../../hooks/use_send_current_request_to_es/send_request_to_es', () => ({ + sendRequestToES: jest.fn(), +})); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx new file mode 100644 index 00000000000000..6162397ce0650e --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -0,0 +1,109 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './editor.test.mock'; + +import React from 'react'; +import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; +import { act } from 'react-dom/test-utils'; +import * as sinon from 'sinon'; + +import { notificationServiceMock } from '../../../../../../../../../../../src/core/public/mocks'; + +import { nextTick } from 'test_utils/enzyme_helpers'; +import { + ServicesContextProvider, + EditorContextProvider, + RequestContextProvider, +} from '../../../../contexts'; + +import { sendRequestToES } from '../../../../hooks/use_send_current_request_to_es/send_request_to_es'; +import * as consoleMenuActions from '../console_menu_actions'; +import { Editor } from './editor'; + +describe('Legacy (Ace) Console Editor Component Smoke Test', () => { + let mockedAppContextValue: any; + const sandbox = sinon.createSandbox(); + + const doMount = () => + mount( + + + + + + + + + + ); + + beforeEach(() => { + document.queryCommandSupported = sinon.fake(() => true); + mockedAppContextValue = { + services: { + history: { + getSavedEditorState: () => null, + updateCurrentState: jest.fn(), + }, + notifications: notificationServiceMock.createSetupContract(), + }, + docLinkVersion: 'NA', + }; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('calls send current request to ES', async () => { + (sendRequestToES as jest.Mock).mockRejectedValue({}); + const editor = doMount(); + act(() => { + editor.find('[data-test-subj~="sendRequestButton"]').simulate('click'); + }); + await nextTick(); + expect(sendRequestToES).toBeCalledTimes(1); + }); + + it('opens docs', () => { + const stub = sandbox.stub(consoleMenuActions, 'getDocumentation'); + const editor = doMount(); + const consoleMenuToggle = editor.find('[data-test-subj~="toggleConsoleMenu"]').last(); + consoleMenuToggle.simulate('click'); + + const docsButton = editor.find('[data-test-subj~="consoleMenuOpenDocs"]').last(); + docsButton.simulate('click'); + + expect(stub.callCount).toBe(1); + }); + + it('prompts auto-indent', () => { + const stub = sandbox.stub(consoleMenuActions, 'autoIndent'); + const editor = doMount(); + const consoleMenuToggle = editor.find('[data-test-subj~="toggleConsoleMenu"]').last(); + consoleMenuToggle.simulate('click'); + + const autoIndentButton = editor.find('[data-test-subj~="consoleMenuAutoIndent"]').last(); + autoIndentButton.simulate('click'); + + expect(stub.callCount).toBe(1); + }); +}); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx new file mode 100644 index 00000000000000..442ed330e9b7a4 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -0,0 +1,203 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react'; +import { EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { useServicesContext, useEditorReadContext } from '../../../../contexts'; +import { useUIAceKeyboardMode } from '../use_ui_ace_keyboard_mode'; +import { ConsoleMenu } from '../../../../components'; + +import { autoIndent, getDocumentation } from '../console_menu_actions'; +import { registerCommands } from './keyboard_shortcuts'; +import { applyCurrentSettings } from './apply_editor_settings'; + +import { useSendCurrentRequestToES, useSetInputEditor } from '../../../../hooks'; + +import * as senseEditor from '../../../../models/sense_editor'; +// @ts-ignore +import mappings from '../../../../../lib/mappings/mappings'; + +import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; + +const abs: CSSProperties = { + position: 'absolute', + top: '0', + left: '0', + bottom: '0', + right: '0', +}; + +const DEFAULT_INPUT_VALUE = `GET _search +{ + "query": { + "match_all": {} + } +}`; + +function EditorUI() { + const { + services: { history, notifications }, + docLinkVersion, + elasticsearchUrl, + } = useServicesContext(); + + const { settings } = useEditorReadContext(); + const setInputEditor = useSetInputEditor(); + const sendCurrentRequestToES = useSendCurrentRequestToES(); + + const editorRef = useRef(null); + const editorInstanceRef = useRef(null); + + const [textArea, setTextArea] = useState(null); + useUIAceKeyboardMode(textArea); + + const openDocumentation = useCallback(async () => { + const documentation = await getDocumentation(editorInstanceRef.current!, docLinkVersion); + if (!documentation) { + return; + } + window.open(documentation, '_blank'); + }, [docLinkVersion]); + + useEffect(() => { + editorInstanceRef.current = senseEditor.create(editorRef.current!); + + const { content: text } = history.getSavedEditorState() || { + content: DEFAULT_INPUT_VALUE, + }; + editorInstanceRef.current.update(text); + + function setupAutosave() { + let timer: number; + const saveDelay = 500; + + editorInstanceRef.current!.getCoreEditor().on('change', () => { + if (timer) { + clearTimeout(timer); + } + timer = window.setTimeout(saveCurrentState, saveDelay); + }); + } + + function saveCurrentState() { + try { + const content = editorInstanceRef.current!.getCoreEditor().getValue(); + history.updateCurrentState(content); + } catch (e) { + // Ignoring saving error + } + } + + setInputEditor(editorInstanceRef.current); + setTextArea(editorRef.current!.querySelector('textarea')); + + mappings.retrieveAutoCompleteInfo(); + + const unsubscribeResizer = subscribeResizeChecker( + editorRef.current!, + editorInstanceRef.current.getCoreEditor() + ); + setupAutosave(); + + return () => { + unsubscribeResizer(); + mappings.clearSubscriptions(); + }; + }, [history, setInputEditor]); + + useEffect(() => { + applyCurrentSettings(editorInstanceRef.current!.getCoreEditor(), settings); + // Preserve legacy focus behavior after settings have updated. + editorInstanceRef + .current!.getCoreEditor() + .getContainer() + .focus(); + }, [settings]); + + useEffect(() => { + registerCommands({ + senseEditor: editorInstanceRef.current!, + sendCurrentRequestToES, + openDocumentation, + }); + }, [sendCurrentRequestToES, openDocumentation]); + + return ( +
+
+
    + + + + + + + + { + return editorInstanceRef.current!.getRequestsAsCURL(elasticsearchUrl); + }} + getDocumentation={() => { + return getDocumentation(editorInstanceRef.current!, docLinkVersion); + }} + autoIndent={(event: any) => { + autoIndent(editorInstanceRef.current!, event); + }} + addNotification={({ title }) => notifications.toasts.add({ title })} + /> + + + + {/* Axe complains about Ace's textarea element missing a label, which interferes with our + automated a11y tests per #52136. This wrapper does nothing to address a11y but it does + satisfy Axe. */} + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} +
+ ); +} + +export const Editor = React.memo(EditorUI); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor_output.tsx new file mode 100644 index 00000000000000..b1398d0a19a8fe --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -0,0 +1,98 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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, { useEffect, useRef } from 'react'; +import { createReadOnlyAceEditor, CustomAceEditor } from '../../../../models/legacy_core_editor'; +import { + useServicesContext, + useEditorReadContext, + useRequestReadContext, +} from '../../../../contexts'; + +import * as utils from '../../../../../lib/utils/utils'; + +import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; +import { applyCurrentSettings } from './apply_editor_settings'; + +function modeForContentType(contentType: string) { + if (contentType.indexOf('application/json') >= 0) { + return 'ace/mode/json'; + } else if (contentType.indexOf('application/yaml') >= 0) { + return 'ace/mode/yaml'; + } + return 'ace/mode/text'; +} + +function EditorOutputUI() { + const editorRef = useRef(null); + const editorInstanceRef = useRef(null); + const { services } = useServicesContext(); + + const { settings: readOnlySettings } = useEditorReadContext(); + const { + lastResult: { data, error }, + } = useRequestReadContext(); + + useEffect(() => { + editorInstanceRef.current = createReadOnlyAceEditor(editorRef.current!); + const unsubscribe = subscribeResizeChecker(editorRef.current!, editorInstanceRef.current); + + return () => { + unsubscribe(); + }; + }, [services.settings]); + + useEffect(() => { + const editor = editorInstanceRef.current!; + if (data) { + const mode = modeForContentType(data[0].response.contentType); + editor.session.setMode(mode); + editor.update( + data + .map(d => d.response.value as string) + .map(readOnlySettings.tripleQuotes ? utils.expandLiteralStrings : a => a) + .join('\n') + ); + } else if (error) { + editor.session.setMode(modeForContentType(error.contentType)); + editor.update(error.value); + } else { + editor.update(''); + } + }, [readOnlySettings, data, error]); + + useEffect(() => { + applyCurrentSettings(editorInstanceRef.current!, readOnlySettings); + }, [readOnlySettings]); + + return ( +
+ {/* Axe complains about Ace's textarea element missing a label, which interferes with our + automated a11y tests per #52136. This wrapper does nothing to address a11y but it does + satisfy Axe. */} + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} +
+ + + + + ); +} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/logo.svg b/src/legacy/core_plugins/console/public/np_ready/application/logo.svg similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/logo.svg rename to src/legacy/core_plugins/console/public/np_ready/application/logo.svg diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/index.ts new file mode 100644 index 00000000000000..366d5b16785560 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/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 './legacy_core_editor/legacy_core_editor'; +export * from './sense_editor'; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/input_tokenization.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/input_tokenization.test.js similarity index 86% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/input_tokenization.test.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/input_tokenization.test.js index 01f30f826ab269..febf472091e374 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/input_tokenization.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/input_tokenization.test.js @@ -16,17 +16,14 @@ * specific language governing permissions and limitations * under the License. */ - -import './setup_mocks'; -import ace from 'brace'; -import 'brace/mode/json'; +import '../legacy_core_editor.test.mocks'; +import RowParser from '../../../../lib/row_parser'; +import { createTokenIterator } from '../../../factories'; import $ from 'jquery'; -import { initializeEditor } from '../../src/input'; - -const tokenIterator = ace.acequire('ace/token_iterator'); +import { create } from '../create'; describe('Input Tokenization', () => { - let input; + let coreEditor; beforeEach(() => { // Set up our document body document.body.innerHTML = @@ -36,33 +33,25 @@ describe('Input Tokenization', () => {
`; - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions'), - ); - - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions'), - ); - input.$el.show(); - input.autocomplete._test.removeChangeListener(); + coreEditor = create(document.querySelector('#ConAppEditor')); + + $(coreEditor.getContainer()).show(); }); afterEach(() => { - input.$el.hide(); - input.autocomplete._test.addChangeListener(); + $(coreEditor.getContainer()).hide(); }); function tokensAsList() { - const iter = new tokenIterator.TokenIterator(input.getSession(), 0, 0); + const iter = createTokenIterator({ editor: coreEditor, position: { lineNumber: 1, column: 1 } }); const ret = []; let t = iter.getCurrentToken(); - if (input.parser.isEmptyToken(t)) { - t = input.parser.nextNonEmptyToken(iter); + const parser = new RowParser(coreEditor); + if (parser.isEmptyToken(t)) { + t = parser.nextNonEmptyToken(iter); } while (t) { ret.push({ value: t.value, type: t.type }); - t = input.parser.nextNonEmptyToken(iter); + t = parser.nextNonEmptyToken(iter); } return ret; @@ -83,18 +72,15 @@ describe('Input Tokenization', () => { data = prefix; } - test('Token test ' + testCount++ + ' prefix: ' + prefix, async function (done) { - input.update(data, function () { - const tokens = tokensAsList(); - const normTokenList = []; - for (let i = 0; i < tokenList.length; i++) { - normTokenList.push({ type: tokenList[i++], value: tokenList[i] }); - } - - expect(tokens).toEqual(normTokenList); - done(); - }); + test('Token test ' + testCount++ + ' prefix: ' + prefix, async function () { + await coreEditor.setValue(data, true); + const tokens = tokensAsList(); + const normTokenList = []; + for (let i = 0; i < tokenList.length; i++) { + normTokenList.push({ type: tokenList[i++], value: tokenList[i] }); + } + expect(tokens).toEqual(normTokenList); }); } @@ -271,10 +257,8 @@ describe('Input Tokenization', () => { function statesAsList() { const ret = []; - const session = input.getSession(); - const maxLine = session.getLength(); - for (let row = 0; row < maxLine; row++) ret.push(session.getState(row)); - + const maxLine = coreEditor.getLineCount(); + for (let line = 1; line <= maxLine; line++) ret.push(coreEditor.getLineState(line)); return ret; } @@ -292,12 +276,10 @@ describe('Input Tokenization', () => { data = prefix; } - test('States test ' + testCount++ + ' prefix: ' + prefix, async function (done) { - input.update(data, function () { - const modes = statesAsList(); - expect(modes).toEqual(statesList); - done(); - }); + test('States test ' + testCount++ + ' prefix: ' + prefix, async function () { + await coreEditor.setValue(data, true); + const modes = statesAsList(); + expect(modes).toEqual(statesList); }); } diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/output_tokenization.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/output_tokenization.test.js similarity index 89% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/output_tokenization.test.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/output_tokenization.test.js index 36c081f5ef9234..b860f691dadb2a 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/output_tokenization.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/output_tokenization.test.js @@ -16,26 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import ace from 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; - const $ = require('jquery'); -const RowParser = require('../../src/sense_editor/row_parser'); - -import { initializeOutput } from '../../src/output'; +import RowParser from '../../../../lib/row_parser'; +import ace from 'brace'; +import { createReadOnlyAceEditor } from '../create_readonly'; let output; - const tokenIterator = ace.acequire('ace/token_iterator'); describe('Output Tokenization', () => { beforeEach(() => { - output = initializeOutput($('#ConAppOutput')); - output.$el.show(); + output = createReadOnlyAceEditor(document.querySelector('#ConAppOutput')); + $(output.container).show(); }); + afterEach(() => { - output.$el.hide(); + $(output.container).hide(); }); function tokensAsList() { diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.ts new file mode 100644 index 00000000000000..8e03182621c830 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.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 ace from 'brace'; +import { LegacyCoreEditor } from './legacy_core_editor'; + +export const create = (el: HTMLElement) => { + const actions = document.querySelector('#ConAppEditorActions'); + if (!actions) { + throw new Error('Could not find ConAppEditorActions element!'); + } + const aceEditor = ace.edit(el); + return new LegacyCoreEditor(aceEditor, actions); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create_readonly.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create_readonly.ts new file mode 100644 index 00000000000000..ce8ededd0b12c5 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create_readonly.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 _ from 'lodash'; +import ace from 'brace'; +// @ts-ignore +import * as OutputMode from './mode/output'; +import smartResize from './smart_resize'; + +export interface CustomAceEditor extends ace.Editor { + update: (text: string, mode?: any, cb?: () => void) => void; + append: (text: string, foldPrevious?: boolean, cb?: () => void) => void; +} + +/** + * Note: using read-only ace editor leaks the Ace editor API - use this as sparingly as possible or + * create an interface for it so that we don't rely directly on vendor APIs. + */ +export function createReadOnlyAceEditor(element: HTMLElement): CustomAceEditor { + const output: CustomAceEditor = ace.acequire('ace/ace').edit(element); + + const outputMode = new OutputMode.Mode(); + + output.$blockScrolling = Infinity; + output.resize = smartResize(output); + output.update = (val: string, mode?: any, cb?: () => void) => { + if (typeof mode === 'function') { + cb = mode; + mode = void 0; + } + + const session = output.getSession(); + + session.setMode(val ? mode || outputMode : 'ace/mode/text'); + session.setValue(val); + if (typeof cb === 'function') { + setTimeout(cb); + } + }; + + output.append = (val: string, foldPrevious?: boolean, cb?: () => void) => { + if (typeof foldPrevious === 'function') { + cb = foldPrevious; + foldPrevious = true; + } + if (_.isUndefined(foldPrevious)) { + foldPrevious = true; + } + const session = output.getSession(); + const lastLine = session.getLength(); + if (foldPrevious) { + output.moveCursorTo(Math.max(0, lastLine - 1), 0); + } + session.insert({ row: lastLine, column: 0 }, '\n' + val); + output.moveCursorTo(lastLine + 1, 0); + if (typeof cb === 'function') { + setTimeout(cb); + } + }; + + // eslint-disable-next-line + (function setupSession(session) { + session.setMode('ace/mode/text'); + (session as any).setFoldStyle('markbeginend'); + session.setTabSize(2); + session.setUseWrapMode(true); + })(output.getSession()); + + output.setShowPrintMargin(false); + output.setReadOnly(true); + + return output; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/index.ts new file mode 100644 index 00000000000000..7655bbcd106d25 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/index.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 'brace'; +import 'brace/ext/language_tools'; +import 'brace/ext/searchbox'; +import 'brace/mode/json'; +import 'brace/mode/text'; + +export * from './legacy_core_editor'; +export * from './create_readonly'; +export * from './create'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts new file mode 100644 index 00000000000000..66673ff42e4e89 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +jest.mock('./mode/worker', () => { + return { workerModule: { id: 'sense_editor/mode/worker', src: '' } }; +}); + +// @ts-ignore +window.Worker = function() { + this.postMessage = () => {}; + (this as any).terminate = () => {}; +}; + +// @ts-ignore +window.URL = { + createObjectURL: () => { + return ''; + }, +}; + +import 'brace'; +import 'brace/ext/language_tools'; +import 'brace/ext/searchbox'; +import 'brace/mode/json'; +import 'brace/mode/text'; + +document.queryCommandSupported = () => true; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts new file mode 100644 index 00000000000000..621f4eeb0163e3 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts @@ -0,0 +1,340 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import ace from 'brace'; +import { Editor as IAceEditor } from 'brace'; +import $ from 'jquery'; +import { CoreEditor, Position, Range, Token, TokensProvider, EditorEvent } from '../../../types'; +import { AceTokensProvider } from '../../../lib/ace_token_provider'; +import * as curl from '../sense_editor/curl'; +import smartResize from './smart_resize'; + +// @ts-ignore +import * as InputMode from './mode/input'; + +const _AceRange = ace.acequire('ace/range').Range; + +const rangeToAceRange = ({ start, end }: Range) => + new _AceRange(start.lineNumber - 1, start.column - 1, end.lineNumber - 1, end.column - 1); + +export class LegacyCoreEditor implements CoreEditor { + private _aceOnPaste: any; + $actions: any; + resize: () => void; + + constructor(private readonly editor: IAceEditor, actions: HTMLElement) { + this.$actions = $(actions); + this.editor.setShowPrintMargin(false); + + const session = this.editor.getSession(); + session.setMode(new InputMode.Mode()); + (session as any).setFoldStyle('markbeginend'); + session.setTabSize(2); + session.setUseWrapMode(true); + + this.resize = smartResize(this.editor); + + // Intercept ace on paste handler. + this._aceOnPaste = this.editor.onPaste; + this.editor.onPaste = this.DO_NOT_USE_onPaste.bind(this); + + this.editor.setOptions({ + enableBasicAutocompletion: true, + }); + + this.editor.$blockScrolling = Infinity; + this.hideActionsBar(); + this.editor.focus(); + } + + // dirty check for tokenizer state, uses a lot less cycles + // than listening for tokenizerUpdate + waitForLatestTokens(): Promise { + return new Promise(resolve => { + const session = this.editor.getSession(); + const checkInterval = 25; + + const check = () => { + // If the bgTokenizer doesn't exist, we can assume that the underlying editor has been + // torn down, e.g. by closing the History tab, and we don't need to do anything further. + if (session.bgTokenizer) { + // Wait until the bgTokenizer is done running before executing the callback. + if ((session.bgTokenizer as any).running) { + setTimeout(check, checkInterval); + } else { + resolve(); + } + } + }; + + setTimeout(check, 0); + }); + } + + getLineState(lineNumber: number) { + const session = this.editor.getSession(); + return session.getState(lineNumber - 1); + } + + getValueInRange(range: Range): string { + return this.editor.getSession().getTextRange(rangeToAceRange(range)); + } + + getTokenProvider(): TokensProvider { + return new AceTokensProvider(this.editor.getSession()); + } + + getValue(): string { + return this.editor.getValue(); + } + + setValue(text: string, forceRetokenize: boolean): Promise { + const session = this.editor.getSession(); + session.setValue(text); + return new Promise(resolve => { + if (!forceRetokenize) { + // resolve immediately + resolve(); + return; + } + + // force update of tokens, but not on this thread to allow for ace rendering. + setTimeout(function() { + let i; + for (i = 0; i < session.getLength(); i++) { + session.getTokens(i); + } + resolve(); + }); + }); + } + + getLineValue(lineNumber: number): string { + const session = this.editor.getSession(); + return session.getLine(lineNumber - 1); + } + + getCurrentPosition(): Position { + const cursorPosition = this.editor.getCursorPosition(); + return { + lineNumber: cursorPosition.row + 1, + column: cursorPosition.column + 1, + }; + } + + clearSelection(): void { + this.editor.clearSelection(); + } + + getTokenAt(pos: Position): Token | null { + const provider = this.getTokenProvider(); + return provider.getTokenAt(pos); + } + + insert(valueOrPos: string | Position, value?: string): void { + if (typeof valueOrPos === 'string') { + this.editor.insert(valueOrPos); + return; + } + const document = this.editor.getSession().getDocument(); + document.insert( + { + column: valueOrPos.column - 1, + row: valueOrPos.lineNumber - 1, + }, + value || '' + ); + } + + moveCursorToPosition(pos: Position): void { + this.editor.moveCursorToPosition({ row: pos.lineNumber - 1, column: pos.column - 1 }); + } + + replace(range: Range, value: string): void { + const session = this.editor.getSession(); + session.replace(rangeToAceRange(range), value); + } + + getLines(startLine: number, endLine: number): string[] { + const session = this.editor.getSession(); + return session.getLines(startLine - 1, endLine - 1); + } + + replaceRange(range: Range, value: string) { + const pos = this.editor.getCursorPosition(); + this.editor.getSession().replace(rangeToAceRange(range), value); + + const maxRow = Math.max(range.start.lineNumber - 1 + value.split('\n').length - 1, 1); + pos.row = Math.min(pos.row, maxRow); + this.editor.moveCursorToPosition(pos); + // ACE UPGRADE - check if needed - at the moment the above may trigger a selection. + this.editor.clearSelection(); + } + + getSelectionRange() { + const result = this.editor.getSelectionRange(); + return { + start: { + lineNumber: result.start.row + 1, + column: result.start.column + 1, + }, + end: { + lineNumber: result.end.row + 1, + column: result.end.column + 1, + }, + }; + } + + getLineCount() { + const text = this.getValue(); + return text.split('\n').length; + } + + addMarker(range: Range) { + return this.editor + .getSession() + .addMarker(rangeToAceRange(range), 'ace_snippet-marker', 'fullLine', false); + } + + removeMarker(ref: any) { + this.editor.getSession().removeMarker(ref); + } + + getWrapLimit(): number { + return this.editor.getSession().getWrapLimit(); + } + + on(event: EditorEvent, listener: () => void) { + if (event === 'changeCursor') { + this.editor.getSession().selection.on(event, listener); + } else if (event === 'changeSelection') { + this.editor.on(event, listener); + } else { + this.editor.getSession().on(event, listener); + } + } + + off(event: EditorEvent, listener: () => void) { + if (event === 'changeSelection') { + this.editor.off(event, listener); + } + } + + isCompleterActive() { + // Secrets of the arcane here. + return Boolean((this.editor as any).completer && (this.editor as any).completer.activated); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + private DO_NOT_USE_onPaste(text: string) { + if (text && curl.detectCURL(text)) { + const curlInput = curl.parseCURL(text); + this.editor.insert(curlInput); + return; + } + this._aceOnPaste.call(this.editor, text); + } + + private setActionsBar = (top?: any) => { + if (top === null) { + this.$actions.css('visibility', 'hidden'); + } else { + this.$actions.css({ + top, + visibility: 'visible', + }); + } + }; + + private hideActionsBar = () => { + this.setActionsBar(); + }; + + execCommand(cmd: string) { + this.editor.execCommand(cmd); + } + + getContainer(): HTMLDivElement { + return this.editor.container as HTMLDivElement; + } + + setStyles(styles: { wrapLines: boolean; fontSize: string }) { + this.editor.getSession().setUseWrapMode(styles.wrapLines); + this.editor.container.style.fontSize = styles.fontSize; + } + + registerKeyboardShortcut(opts: { keys: string; fn: () => void; name: string }): void { + this.editor.commands.addCommand({ + exec: opts.fn, + name: opts.name, + bindKey: opts.keys, + }); + } + + legacyUpdateUI(range: any) { + if (!this.$actions) { + return; + } + if (range) { + // elements are positioned relative to the editor's container + // pageY is relative to page, so subtract the offset + // from pageY to get the new top value + const offsetFromPage = $(this.editor.container).offset()!.top; + const startRow = range.start.lineNumber - 1; + const startColumn = range.start.column; + const firstLine = this.getLineValue(startRow); + const maxLineLength = this.getWrapLimit() - 5; + const isWrapping = firstLine.length > maxLineLength; + const getScreenCoords = (row: number) => + this.editor.renderer.textToScreenCoordinates(row, startColumn).pageY - offsetFromPage; + const topOfReq = getScreenCoords(startRow); + + if (topOfReq >= 0) { + let offset = 0; + if (isWrapping) { + // Try get the line height of the text area in pixels. + const textArea = $(this.editor.container.querySelector('textArea')!); + const hasRoomOnNextLine = this.getLineValue(startRow + 1).length < maxLineLength; + if (textArea && hasRoomOnNextLine) { + // Line height + the number of wraps we have on a line. + offset += this.getLineValue(startRow).length * textArea.height()!; + } else { + if (startRow > 0) { + this.setActionsBar(getScreenCoords(startRow - 1)); + return; + } + this.setActionsBar(getScreenCoords(startRow + 1)); + return; + } + } + this.setActionsBar(topOfReq + offset); + return; + } + + const bottomOfReq = + this.editor.renderer.textToScreenCoordinates(range.end.lineNumber, range.end.column).pageY - + offsetFromPage; + + if (bottomOfReq >= 0) { + this.setActionsBar(0); + return; + } + } + } +} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/index.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/index.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/index.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/index.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/worker.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/worker.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/worker.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/worker.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/x_json_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/x_json_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/x_json_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/x_json_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/smart_resize.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/smart_resize.ts new file mode 100644 index 00000000000000..b88e0e44591d83 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/smart_resize.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. + */ + +import { get, throttle } from 'lodash'; + +// eslint-disable-next-line import/no-default-export +export default function(editor: any) { + const resize = editor.resize; + + const throttledResize = throttle(() => { + resize.call(editor); + + // Keep current top line in view when resizing to avoid losing user context + const userRow = get(throttledResize, 'topRow', 0); + if (userRow !== 0) { + editor.renderer.scrollToLine(userRow, false, false, () => {}); + } + }, 35); + return throttledResize; +} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/theme_sense_dark.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/theme_sense_dark.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/editor_input1.txt b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/editor_input1.txt similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/editor_input1.txt rename to src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/editor_input1.txt diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/integration.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/integration.test.js similarity index 80% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/integration.test.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/integration.test.js index 9df4aa5e0becef..0950992f1c0fef 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/integration.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/integration.test.js @@ -16,51 +16,44 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import 'brace'; -import 'brace/mode/json'; -import { initializeEditor } from '../../src/input'; +import '../sense_editor.test.mocks'; +import { create } from '../create'; import _ from 'lodash'; const $ = require('jquery'); -const kb = require('../../src/kb'); -const mappings = require('../../src/mappings'); - - +const kb = require('../../../../lib/kb/kb'); +const mappings = require('../../../../lib/mappings/mappings'); describe('Integration', () => { - let input; + let senseEditor; beforeEach(() => { // Set up our document body document.body.innerHTML = '
'; - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions') - ); - input.$el.show(); - input.autocomplete._test.removeChangeListener(); + senseEditor = create(document.querySelector('#ConAppEditor')); + $(senseEditor.getCoreEditor().getContainer()).show(); + senseEditor.autocomplete._test.removeChangeListener(); }); afterEach(() => { - input.$el.hide(); - input.autocomplete._test.addChangeListener(); + $(senseEditor.getCoreEditor().getContainer()).hide(); + senseEditor.autocomplete._test.addChangeListener(); }); function processContextTest(data, mapping, kbSchemes, requestLine, testToRun) { test(testToRun.name, async function (done) { - let rowOffset = 0; // add one for the extra method line + let lineOffset = 0; // add one for the extra method line let editorValue = data; if (requestLine != null) { if (data != null) { editorValue = requestLine + '\n' + data; - rowOffset = 1; + lineOffset = 1; } else { editorValue = requestLine; } } - testToRun.cursor.row += rowOffset; + testToRun.cursor.lineNumber += lineOffset; mappings.clear(); mappings.loadMappings(mapping); @@ -81,109 +74,104 @@ describe('Integration', () => { } kb.setActiveApi(testApi); const { cursor } = testToRun; - const { row, column } = cursor; - input.update(editorValue, function () { - input.moveCursorTo(row, column); + await senseEditor.update(editorValue, true); + senseEditor.getCoreEditor().moveCursorToPosition(cursor); - // allow ace rendering to move cursor so it will be seen during test - handy for debugging. - //setTimeout(function () { - input.completer = { - base: {}, - changeListener: function () {}, - }; // mimic auto complete + // allow ace rendering to move cursor so it will be seen during test - handy for debugging. + //setTimeout(function () { + senseEditor.completer = { + base: {}, + changeListener: function () {}, + }; // mimic auto complete - input.autocomplete._test.getCompletions( - input, - input.getSession(), - cursor, - '', - function (err, terms) { - if (testToRun.assertThrows) { - done(); - return; - } + senseEditor.autocomplete._test.getCompletions( + senseEditor, + null, + { row: cursor.lineNumber - 1, column: cursor.column - 1 }, + '', + function (err, terms) { + if (testToRun.assertThrows) { + done(); + return; + } - if (err) { - done(); - throw err; - } + if (err) { + throw err; + } - if (testToRun.no_context) { - expect(!terms || terms.length === 0).toBeTruthy(); - } else { - expect(terms).not.toBeNull(); - expect(terms.length).toBeGreaterThan(0); - } + if (testToRun.no_context) { + expect(!terms || terms.length === 0).toBeTruthy(); + } else { + expect(terms).not.toBeNull(); + expect(terms.length).toBeGreaterThan(0); + } - if (!terms || terms.length === 0) { - done(); - return; - } + if (!terms || terms.length === 0) { + done(); + return; + } - if (testToRun.autoCompleteSet) { - const expectedTerms = _.map(testToRun.autoCompleteSet, function (t) { - if (typeof t !== 'object') { - t = { name: t }; - } - return t; - }); - if (terms.length !== expectedTerms.length) { - expect(_.pluck(terms, 'name')).toEqual(_.pluck(expectedTerms, 'name')); - } else { - const filteredActualTerms = _.map(terms, function ( - actualTerm, - i - ) { - const expectedTerm = expectedTerms[i]; - const filteredTerm = {}; - _.each(expectedTerm, function (v, p) { - filteredTerm[p] = actualTerm[p]; - }); - return filteredTerm; - }); - expect(filteredActualTerms).toEqual(expectedTerms); + if (testToRun.autoCompleteSet) { + const expectedTerms = _.map(testToRun.autoCompleteSet, function (t) { + if (typeof t !== 'object') { + t = { name: t }; } - done(); + return t; + }); + if (terms.length !== expectedTerms.length) { + expect(_.pluck(terms, 'name')).toEqual(_.pluck(expectedTerms, 'name')); + } else { + const filteredActualTerms = _.map(terms, function ( + actualTerm, + i + ) { + const expectedTerm = expectedTerms[i]; + const filteredTerm = {}; + _.each(expectedTerm, function (v, p) { + filteredTerm[p] = actualTerm[p]; + }); + return filteredTerm; + }); + expect(filteredActualTerms).toEqual(expectedTerms); } + } - const context = terms[0].context; - const { cursor: { row, column } } = testToRun; - input.autocomplete._test.addReplacementInfoToContext( - context, - { lineNumber: row + 1, column: column + 1 }, - terms[0].value - ); + const context = terms[0].context; + const { cursor: { lineNumber, column } } = testToRun; + senseEditor.autocomplete._test.addReplacementInfoToContext( + context, + { lineNumber, column }, + terms[0].value + ); - function ac(prop, propTest) { - if (typeof testToRun[prop] !== 'undefined') { - if (propTest) { - propTest(context[prop], testToRun[prop], prop); - } else { - expect(context[prop]).toEqual(testToRun[prop]); - } + function ac(prop, propTest) { + if (typeof testToRun[prop] !== 'undefined') { + if (propTest) { + propTest(context[prop], testToRun[prop], prop); + } else { + expect(context[prop]).toEqual(testToRun[prop]); } } + } - function posCompare(actual, expected) { - expect(actual.lineNumber).toEqual(expected.lineNumber + rowOffset); - expect(actual.column).toEqual(expected.column); - } - - function rangeCompare(actual, expected, name) { - posCompare(actual.start, expected.start, name + '.start'); - posCompare(actual.end, expected.end, name + '.end'); - } + function posCompare(actual, expected) { + expect(actual.lineNumber).toEqual(expected.lineNumber + lineOffset); + expect(actual.column).toEqual(expected.column); + } - ac('prefixToAdd'); - ac('suffixToAdd'); - ac('addTemplate'); - ac('textBoxPosition', posCompare); - ac('rangeToReplace', rangeCompare); - done(); + function rangeCompare(actual, expected, name) { + posCompare(actual.start, expected.start, name + '.start'); + posCompare(actual.end, expected.end, name + '.end'); } - ); - //}); - }); + + ac('prefixToAdd'); + ac('suffixToAdd'); + ac('addTemplate'); + ac('textBoxPosition', posCompare); + ac('rangeToReplace', rangeCompare); + done(); + } + ); }); } @@ -236,7 +224,7 @@ describe('Integration', () => { contextTests({}, MAPPING, SEARCH_KB, 'POST _search', [ { name: 'Empty doc', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -252,7 +240,7 @@ describe('Integration', () => { contextTests({}, MAPPING, SEARCH_KB, 'POST _no_context', [ { name: 'Missing KB', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, no_context: true, }, ]); @@ -276,7 +264,7 @@ describe('Integration', () => { [ { name: 'Missing KB - global auto complete', - cursor: { row: 2, column: 5 }, + cursor: { lineNumber: 3, column: 6 }, autoCompleteSet: ['t1'], }, ] @@ -296,7 +284,7 @@ describe('Integration', () => { [ { name: 'existing dictionary key, no template', - cursor: { row: 1, column: 6 }, + cursor: { lineNumber: 2, column: 6 }, initialValue: 'query', addTemplate: false, prefixToAdd: '', @@ -309,7 +297,7 @@ describe('Integration', () => { }, { name: 'existing inner dictionary key', - cursor: { row: 2, column: 7 }, + cursor: { lineNumber: 3, column: 8 }, initialValue: 'field', addTemplate: false, prefixToAdd: '', @@ -322,7 +310,7 @@ describe('Integration', () => { }, { name: 'existing dictionary key, yes template', - cursor: { row: 4, column: 7 }, + cursor: { lineNumber: 5, column: 8 }, initialValue: 'facets', addTemplate: true, prefixToAdd: '', @@ -335,13 +323,12 @@ describe('Integration', () => { }, { name: 'ignoring meta keys', - cursor: { row: 4, column: 14 }, + cursor: { lineNumber: 5, column: 15 }, no_context: true, }, ] ); - contextTests( '{\n' + ' "query": {\n' + @@ -356,7 +343,7 @@ describe('Integration', () => { [ { name: 'trailing comma, end of line', - cursor: { row: 4, column: 16 }, + cursor: { lineNumber: 5, column: 17 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -369,7 +356,7 @@ describe('Integration', () => { }, { name: 'trailing comma, beginning of line', - cursor: { row: 5, column: 1 }, + cursor: { lineNumber: 6, column: 2 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -382,7 +369,7 @@ describe('Integration', () => { }, { name: 'prefix comma, beginning of line', - cursor: { row: 6, column: 0 }, + cursor: { lineNumber: 7, column: 1 }, initialValue: '', addTemplate: true, prefixToAdd: ', ', @@ -395,7 +382,7 @@ describe('Integration', () => { }, { name: 'prefix comma, end of line', - cursor: { row: 5, column: 14 }, + cursor: { lineNumber: 6, column: 15 }, initialValue: '', addTemplate: true, prefixToAdd: ', ', @@ -437,31 +424,31 @@ describe('Integration', () => { [ { name: 'not matching object when { is not opened', - cursor: { row: 1, column: 12 }, + cursor: { lineNumber: 2, column: 13 }, initialValue: '', autoCompleteSet: ['{'], }, { name: 'not matching array when [ is not opened', - cursor: { row: 2, column: 12 }, + cursor: { lineNumber: 3, column: 13 }, initialValue: '', autoCompleteSet: ['['], }, { name: 'matching value with one_of', - cursor: { row: 3, column: 19 }, + cursor: { lineNumber: 4, column: 20 }, initialValue: '', autoCompleteSet: [1, 2], }, { name: 'matching value', - cursor: { row: 4, column: 12 }, + cursor: { lineNumber: 5, column: 13 }, initialValue: '', autoCompleteSet: [3], }, { name: 'matching any value with one_of', - cursor: { row: 5, column: 21 }, + cursor: { lineNumber: 6, column: 22 }, initialValue: '', autoCompleteSet: [4, 5], }, @@ -484,7 +471,7 @@ describe('Integration', () => { [ { name: '* matching everything', - cursor: { row: 5, column: 15 }, + cursor: { lineNumber: 6, column: 16 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -517,7 +504,7 @@ describe('Integration', () => { [ { name: '{index} matching', - cursor: { row: 1, column: 15 }, + cursor: { lineNumber: 2, column: 16 }, autoCompleteSet: [ { name: 'index1', meta: 'index' }, { name: 'index2', meta: 'index' }, @@ -558,7 +545,7 @@ describe('Integration', () => { [ { name: 'Templates 1', - cursor: { row: 1, column: 0 }, + cursor: { lineNumber: 2, column: 1 }, autoCompleteSet: [ tt('array', []), tt('fixed', { a: 1 }), @@ -569,7 +556,7 @@ describe('Integration', () => { }, { name: 'Templates - one off', - cursor: { row: 4, column: 12 }, + cursor: { lineNumber: 5, column: 13 }, autoCompleteSet: [tt('o1'), tt('o2')], }, ] @@ -612,7 +599,7 @@ describe('Integration', () => { [ { name: 'Conditionals', - cursor: { row: 2, column: 15 }, + cursor: { lineNumber: 3, column: 16 }, autoCompleteSet: [tt('always', {}), tt('match', {})], }, ] @@ -655,7 +642,7 @@ describe('Integration', () => { [ { name: 'Any of - templates', - cursor: { row: 1, column: 0 }, + cursor: { lineNumber: 2, column: 1 }, autoCompleteSet: [ tt('any_of_mixed', []), tt('any_of_numbers', [1, 2]), @@ -664,22 +651,22 @@ describe('Integration', () => { }, { name: 'Any of - numbers', - cursor: { row: 2, column: 2 }, + cursor: { lineNumber: 3, column: 3 }, autoCompleteSet: [1, 2, 3], }, { name: 'Any of - object', - cursor: { row: 6, column: 2 }, + cursor: { lineNumber: 7, column: 3 }, autoCompleteSet: [tt('a', 1), tt('b', 2), tt('c', 1)], }, { name: 'Any of - mixed - obj', - cursor: { row: 11, column: 2 }, + cursor: { lineNumber: 12, column: 3 }, autoCompleteSet: [tt('a', 1)], }, { name: 'Any of - mixed - both', - cursor: { row: 13, column: 2 }, + cursor: { lineNumber: 14, column: 3 }, autoCompleteSet: [tt('{'), tt(3)], }, ] @@ -702,7 +689,7 @@ describe('Integration', () => { [ { name: 'Empty string as default', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, autoCompleteSet: [tt('query', '')], }, ] @@ -785,7 +772,7 @@ describe('Integration', () => { [ { name: 'Relative scope link test', - cursor: { row: 2, column: 12 }, + cursor: { lineNumber: 3, column: 13 }, autoCompleteSet: [ tt('b', {}), tt('c', {}), @@ -798,37 +785,37 @@ describe('Integration', () => { }, { name: 'External scope link test', - cursor: { row: 3, column: 12 }, + cursor: { lineNumber: 4, column: 13 }, autoCompleteSet: [tt('t2', 1)], }, { name: 'Global scope link test', - cursor: { row: 4, column: 12 }, + cursor: { lineNumber: 5, column: 13 }, autoCompleteSet: [tt('t1', 2), tt('t1a', {})], }, { name: 'Global scope link with an internal scope link', - cursor: { row: 5, column: 17 }, + cursor: { lineNumber: 6, column: 18 }, autoCompleteSet: [tt('t1', 2), tt('t1a', {})], }, { name: 'Entire endpoint scope link test', - cursor: { row: 7, column: 12 }, + cursor: { lineNumber: 8, column: 13 }, autoCompleteSet: [tt('target', {})], }, { name: 'A scope link within an array', - cursor: { row: 9, column: 10 }, + cursor: { lineNumber: 10, column: 11 }, autoCompleteSet: [tt('t2', 1)], }, { name: 'A function based scope link', - cursor: { row: 11, column: 12 }, + cursor: { lineNumber: 12, column: 13 }, autoCompleteSet: [tt('a', 1), tt('b', 2)], }, { name: 'A global scope link with wrong link', - cursor: { row: 12, column: 12 }, + cursor: { lineNumber: 13, column: 13 }, assertThrows: /broken/, }, ] @@ -857,7 +844,7 @@ describe('Integration', () => { [ { name: 'Top level scope link', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, autoCompleteSet: [tt('t1', 2)], }, ] @@ -883,7 +870,7 @@ describe('Integration', () => { [ { name: 'Path after empty object', - cursor: { row: 1, column: 10 }, + cursor: { lineNumber: 2, column: 11 }, autoCompleteSet: ['a', 'b'], }, ] @@ -899,7 +886,7 @@ describe('Integration', () => { [ { name: 'Replace an empty string', - cursor: { row: 1, column: 4 }, + cursor: { lineNumber: 2, column: 5 }, rangeToReplace: { start: { lineNumber: 2, column: 4 }, end: { lineNumber: 2, column: 10 }, @@ -931,12 +918,12 @@ describe('Integration', () => { [ { name: 'List of objects - internal autocomplete', - cursor: { row: 3, column: 10 }, + cursor: { lineNumber: 4, column: 11 }, autoCompleteSet: ['b'], }, { name: 'List of objects - external template', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, autoCompleteSet: [tt('a', [{}])], }, ] @@ -964,7 +951,7 @@ describe('Integration', () => { [ { name: 'Field completion as scope', - cursor: { row: 3, column: 10 }, + cursor: { lineNumber: 4, column: 11 }, autoCompleteSet: [ tt('field1.1.1', { f: 1 }, 'string'), tt('field1.1.2', { f: 1 }, 'string'), @@ -972,7 +959,7 @@ describe('Integration', () => { }, { name: 'Field completion as value', - cursor: { row: 9, column: 23 }, + cursor: { lineNumber: 10, column: 24 }, autoCompleteSet: [ { name: 'field1.1.1', meta: 'string' }, { name: 'field1.1.2', meta: 'string' }, @@ -985,7 +972,7 @@ describe('Integration', () => { contextTests('POST _search\n', MAPPING, SEARCH_KB, null, [ { name: 'initial doc start', - cursor: { row: 1, column: 0 }, + cursor: { lineNumber: 2, column: 1 }, autoCompleteSet: ['{'], prefixToAdd: '', suffixToAdd: '', @@ -1000,14 +987,14 @@ describe('Integration', () => { [ { name: 'Cursor rows after request end', - cursor: { row: 4, column: 0 }, + cursor: { lineNumber: 5, column: 1 }, autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], prefixToAdd: '', suffixToAdd: ' ', }, { name: 'Cursor just after request end', - cursor: { row: 2, column: 1 }, + cursor: { lineNumber: 3, column: 2 }, no_context: true, }, ] @@ -1041,7 +1028,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster', [ { name: 'Endpoints with slashes - no slash', - cursor: { row: 0, column: 8 }, + cursor: { lineNumber: 1, column: 9 }, autoCompleteSet: [ '_cluster/nodes/stats', '_cluster/stats', @@ -1057,7 +1044,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster/', [ { name: 'Endpoints with slashes - before slash', - cursor: { row: 0, column: 7 }, + cursor: { lineNumber: 1, column: 8 }, autoCompleteSet: [ '_cluster/nodes/stats', '_cluster/stats', @@ -1070,7 +1057,7 @@ describe('Integration', () => { }, { name: 'Endpoints with slashes - on slash', - cursor: { row: 0, column: 12 }, + cursor: { lineNumber: 1, column: 13 }, autoCompleteSet: [ '_cluster/nodes/stats', '_cluster/stats', @@ -1083,7 +1070,7 @@ describe('Integration', () => { }, { name: 'Endpoints with slashes - after slash', - cursor: { row: 0, column: 13 }, + cursor: { lineNumber: 1, column: 14 }, autoCompleteSet: ['nodes/stats', 'stats'], prefixToAdd: '', suffixToAdd: '', @@ -1093,7 +1080,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster/no', [ { name: 'Endpoints with slashes - after slash', - cursor: { row: 0, column: 14 }, + cursor: { lineNumber: 1, column: 15 }, autoCompleteSet: [ { name: 'nodes/stats', meta: 'endpoint' }, { name: 'stats', meta: 'endpoint' }, @@ -1107,7 +1094,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster/nodes/st', [ { name: 'Endpoints with two slashes', - cursor: { row: 0, column: 20 }, + cursor: { lineNumber: 1, column: 21 }, autoCompleteSet: ['stats'], prefixToAdd: '', suffixToAdd: '', @@ -1118,7 +1105,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET ', [ { name: 'Immediately after space + method', - cursor: { row: 0, column: 4 }, + cursor: { lineNumber: 1, column: 5 }, autoCompleteSet: [ { name: '_cluster/nodes/stats', meta: 'endpoint' }, { name: '_cluster/stats', meta: 'endpoint' }, @@ -1135,7 +1122,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET cl', [ { name: 'Endpoints by subpart GET', - cursor: { row: 0, column: 6 }, + cursor: { lineNumber: 1, column: 7 }, autoCompleteSet: [ { name: '_cluster/nodes/stats', meta: 'endpoint' }, { name: '_cluster/stats', meta: 'endpoint' }, @@ -1153,7 +1140,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'POST cl', [ { name: 'Endpoints by subpart POST', - cursor: { row: 0, column: 7 }, + cursor: { lineNumber: 1, column: 8 }, no_context: true, prefixToAdd: '', suffixToAdd: '', @@ -1164,7 +1151,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?', [ { name: 'Params just after ?', - cursor: { row: 0, column: 12 }, + cursor: { lineNumber: 1, column: 13 }, autoCompleteSet: [ { name: 'filter_path', meta: 'param', insertValue: 'filter_path=' }, { name: 'format', meta: 'param', insertValue: 'format=' }, @@ -1180,7 +1167,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?format=', [ { name: 'Params values', - cursor: { row: 0, column: 19 }, + cursor: { lineNumber: 1, column: 20 }, autoCompleteSet: [ { name: 'json', meta: 'format' }, { name: 'yaml', meta: 'format' }, @@ -1193,7 +1180,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?format=yaml&', [ { name: 'Params after amp', - cursor: { row: 0, column: 24 }, + cursor: { lineNumber: 1, column: 25 }, autoCompleteSet: [ { name: 'filter_path', meta: 'param', insertValue: 'filter_path=' }, { name: 'format', meta: 'param', insertValue: 'format=' }, @@ -1209,7 +1196,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?format=yaml&search', [ { name: 'Params on existing param', - cursor: { row: 0, column: 26 }, + cursor: { lineNumber: 1, column: 27 }, rangeToReplace: { start: { lineNumber: 1, column: 25 }, end: { lineNumber: 1, column: 31 }, @@ -1234,7 +1221,7 @@ describe('Integration', () => { [ { name: 'Params on existing value', - cursor: { row: 0, column: 37 }, + cursor: { lineNumber: 1, column: 38 }, rangeToReplace: { start: { lineNumber: 1, column: 37 }, end: { lineNumber: 1, column: 40 }, @@ -1257,7 +1244,7 @@ describe('Integration', () => { [ { name: 'Params on just after = with existing value', - cursor: { row: 0, column: 36 }, + cursor: { lineNumber: 1, column: 37 }, rangeToReplace: { start: { lineNumber: 1, column: 37 }, end: { lineNumber: 1, column: 37 }, @@ -1286,7 +1273,7 @@ describe('Integration', () => { [ { name: 'fullurl - existing dictionary key, no template', - cursor: { row: 1, column: 6 }, + cursor: { lineNumber: 2, column: 7 }, initialValue: 'query', addTemplate: false, prefixToAdd: '', @@ -1299,7 +1286,7 @@ describe('Integration', () => { }, { name: 'fullurl - existing inner dictionary key', - cursor: { row: 2, column: 7 }, + cursor: { lineNumber: 3, column: 8 }, initialValue: 'field', addTemplate: false, prefixToAdd: '', @@ -1312,7 +1299,7 @@ describe('Integration', () => { }, { name: 'fullurl - existing dictionary key, yes template', - cursor: { row: 4, column: 7 }, + cursor: { lineNumber: 5, column: 8 }, initialValue: 'facets', addTemplate: true, prefixToAdd: '', diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/sense_editor.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/sense_editor.test.js new file mode 100644 index 00000000000000..ae66b1aa8e755d --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/sense_editor.test.js @@ -0,0 +1,488 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import '../sense_editor.test.mocks'; + +import $ from 'jquery'; +import _ from 'lodash'; + +import { create } from '../create'; +const editorInput1 = require('./editor_input1.txt'); +const utils = require('../../../../lib/utils/utils'); + +describe('Editor', () => { + let input; + + beforeEach(function () { + // Set up our document body + document.body.innerHTML = + `
+
+
+
+
`; + + input = create( + document.querySelector('#ConAppEditor') + ); + $(input.getCoreEditor().getContainer()).show(); + input.autocomplete._test.removeChangeListener(); + }); + afterEach(function () { + $(input.getCoreEditor().getContainer()).hide(); + input.autocomplete._test.addChangeListener(); + }); + + let testCount = 0; + + const callWithEditorMethod = (editorMethod, fn) => async (done) => { + const results = await input[editorMethod](); + fn(results, done); + }; + + function utilsTest(name, prefix, data, testToRun) { + const id = testCount++; + if (typeof data === 'function') { + testToRun = data; + data = null; + } + if (data && typeof data !== 'string') { + data = JSON.stringify(data, null, 3); + } + if (data) { + if (prefix) { + data = prefix + '\n' + data; + } + } else { + data = prefix; + } + + test('Utils test ' + id + ' : ' + name, async function (done) { + await input.update(data, true); + testToRun(done); + }); + } + + function compareRequest(requests, expected) { + if (!Array.isArray(requests)) { + requests = [requests]; + expected = [expected]; + } + + _.each(requests, function (r) { + delete r.range; + }); + expect(requests).toEqual(expected); + } + + const simpleRequest = { + prefix: 'POST _search', + data: ['{', ' "query": { "match_all": {} }', '}'].join('\n'), + }; + + const singleLineRequest = { + prefix: 'POST _search', + data: '{ "query": { "match_all": {} } }', + }; + + const getRequestNoData = { + prefix: 'GET _stats', + }; + + const multiDocRequest = { + prefix: 'POST _bulk', + data_as_array: [ + '{ "index": { "_index": "index", "_type":"type" } }', + '{ "field": 1 }', + ], + }; + multiDocRequest.data = multiDocRequest.data_as_array.join('\n'); + + utilsTest( + 'simple request range', + simpleRequest.prefix, + simpleRequest.data, + callWithEditorMethod('getRequestRange', (range, done) => { + compareRequest(range, { start: { lineNumber: 1, column: 1 }, end: { lineNumber: 4, column: 2 } }); + done(); + }) + ); + + utilsTest( + 'simple request data', + simpleRequest.prefix, + simpleRequest.data, + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'POST', + url: '_search', + data: [simpleRequest.data], + }; + compareRequest(request, expected); + done(); + }) + ); + + utilsTest( + 'simple request range, prefixed with spaces', + ' ' + simpleRequest.prefix, + simpleRequest.data, + callWithEditorMethod('getRequestRange', (range, done) => { + expect(range).toEqual({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 4, column: 2 } + }); + done(); + }) + ); + + utilsTest( + 'simple request data, prefixed with spaces', + ' ' + simpleRequest.prefix, + simpleRequest.data, + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'POST', + url: '_search', + data: [simpleRequest.data], + }; + + compareRequest(request, expected); + done(); + }) + ); + + + utilsTest( + 'simple request range, suffixed with spaces', + simpleRequest.prefix + ' ', + simpleRequest.data + ' ', + callWithEditorMethod('getRequestRange', (range, done) => { + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 4, column: 2 } + }); + done(); + }) + ); + + utilsTest( + 'simple request data, suffixed with spaces', + simpleRequest.prefix + ' ', + simpleRequest.data + ' ', + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'POST', + url: '_search', + data: [simpleRequest.data], + }; + + compareRequest(request, expected); + done(); + }) + ); + + + utilsTest( + 'single line request range', + singleLineRequest.prefix, + singleLineRequest.data, + callWithEditorMethod('getRequestRange', (range, done) => { + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 2, column: 33 } + }); + done(); + }) + ); + + utilsTest( + 'full url: single line request data', + 'POST https://somehost/_search', + singleLineRequest.data, + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'POST', + url: 'https://somehost/_search', + data: [singleLineRequest.data], + }; + compareRequest(request, expected); + done(); + }) + ); + + utilsTest( + 'request with no data followed by a new line', + getRequestNoData.prefix, + '\n', + callWithEditorMethod('getRequestRange', (range, done) => { + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 1, column: 11 }, + }); + done(); + }) + ); + + utilsTest( + 'request with no data followed by a new line (data)', + getRequestNoData.prefix, + '\n', + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'GET', + url: '_stats', + data: [], + }; + compareRequest(request, expected); + done(); + }) + ); + + utilsTest( + 'request with no data', + getRequestNoData.prefix, + getRequestNoData.data, + callWithEditorMethod('getRequestRange', (range, done) => { + expect(range).toEqual({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 1, column: 11 }, + }); + done(); + }) + ); + + utilsTest( + 'request with no data (data)', + getRequestNoData.prefix, + getRequestNoData.data, + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'GET', + url: '_stats', + data: [], + }; + compareRequest(request, expected); + done(); + }) + ); + + utilsTest( + 'multi doc request range', + multiDocRequest.prefix, + multiDocRequest.data, + callWithEditorMethod('getRequestRange', (range, done) => { + expect(range).toEqual({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 3, column: 15 }, + }); + done(); + }) + ); + + utilsTest( + 'multi doc request data', + multiDocRequest.prefix, + multiDocRequest.data, + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'POST', + url: '_bulk', + data: multiDocRequest.data_as_array, + }; + compareRequest(request, expected); + done(); + }) + ); + + const scriptRequest = { + prefix: 'POST _search', + data: [ + '{', + ' "query": { "script": """', + ' some script ', + ' """}', + '}', + ].join('\n'), + }; + + utilsTest( + 'script request range', + scriptRequest.prefix, + scriptRequest.data, + callWithEditorMethod('getRequestRange', (range, done) => { + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 6, column: 2 }, + }); + done(); + }) + ); + + + utilsTest( + 'simple request data', + simpleRequest.prefix, + simpleRequest.data, + callWithEditorMethod('getRequest', (request, done) => { + const expected = { + method: 'POST', + url: '_search', + data: [utils.collapseLiteralStrings(simpleRequest.data)], + }; + + compareRequest(request, expected); + done(); + }) + ); + + function multiReqTest(name, editorInput, range, expected) { + utilsTest('multi request select - ' + name, editorInput, async function (done) { + const requests = await input.getRequestsInRange(range, false); + // convert to format returned by request. + _.each(expected, function (req) { + req.data = + req.data == null ? [] : [JSON.stringify(req.data, null, 2)]; + }); + + compareRequest(requests, expected); + done(); + }); + } + + multiReqTest( + 'mid body to mid body', + editorInput1, + { start: { lineNumber: 13 }, end: { lineNumber: 18 } }, + [ + { + method: 'PUT', + url: 'index_1/type1/1', + data: { + f: 1, + }, + }, + { + method: 'PUT', + url: 'index_1/type1/2', + data: { + f: 2, + }, + }, + ] + ); + + multiReqTest( + 'single request start to end', + editorInput1, + { start: { lineNumber: 11 }, end: { lineNumber: 14 } }, + [ + { + method: 'PUT', + url: 'index_1/type1/1', + data: { + f: 1, + }, + }, + ] + ); + + multiReqTest( + 'start to end, with comment', + editorInput1, + { start: { lineNumber: 7 }, end: { lineNumber: 14 } }, + [ + { + method: 'GET', + url: '_stats?level=shards', + data: null, + }, + { + method: 'PUT', + url: 'index_1/type1/1', + data: { + f: 1, + }, + }, + ] + ); + + multiReqTest( + 'before start to after end, with comments', + editorInput1, + { start: { lineNumber: 5 }, end: { lineNumber: 15 } }, + [ + { + method: 'GET', + url: '_stats?level=shards', + data: null, + }, + { + method: 'PUT', + url: 'index_1/type1/1', + data: { + f: 1, + }, + }, + ] + ); + + multiReqTest( + 'between requests', + editorInput1, + { start: { lineNumber: 22 }, end: { lineNumber: 23 } }, + [] + ); + + multiReqTest( + 'between requests - with comment', + editorInput1, + { start: { lineNumber: 21 }, end: { lineNumber: 23 } }, + [] + ); + + multiReqTest( + 'between requests - before comment', + editorInput1, + { start: { lineNumber: 20 }, end: { lineNumber: 23 } }, + [] + ); + + function multiReqCopyAsCurlTest(name, editorInput, range, expected) { + utilsTest('multi request copy as curl - ' + name, editorInput, async function (done) { + const curl = await input.getRequestsAsCURL('http://localhost:9200', range); + expect(curl).toEqual(expected); + done(); + }); + } + + multiReqCopyAsCurlTest( + 'start to end, with comment', + editorInput1, + { start: { lineNumber: 7 }, end: { lineNumber: 14 } }, + ` +curl -XGET "http://localhost:9200/_stats?level=shards" + +#in between comment + +curl -XPUT "http://localhost:9200/index_1/type1/1" -H 'Content-Type: application/json' -d' +{ + "f": 1 +}'`.trim() + ); +}); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.ts new file mode 100644 index 00000000000000..33508a2f632990 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.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 { SenseEditor } from './sense_editor'; +import * as core from '../legacy_core_editor'; + +export function create(element: HTMLElement) { + const coreEditor = core.create(element); + const senseEditor = new SenseEditor(coreEditor); + + /** + * Init the editor + */ + senseEditor.highlightCurrentRequestsAndUpdateActionBar(); + return senseEditor; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts new file mode 100644 index 00000000000000..d56a9cca0ef325 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +function detectCURLinLine(line: string) { + // returns true if text matches a curl request + return line.match(/^\s*?curl\s+(-X[A-Z]+)?\s*['"]?.*?['"]?(\s*$|\s+?-d\s*?['"])/); +} + +export function detectCURL(text: string) { + // returns true if text matches a curl request + if (!text) return false; + for (const line of text.split('\n')) { + if (detectCURLinLine(line)) { + return true; + } + } + return false; +} + +export function parseCURL(text: string) { + let state = 'NONE'; + const out = []; + let body: any[] = []; + let line = ''; + const lines = text.trim().split('\n'); + let matches; + + const EmptyLine = /^\s*$/; + const Comment = /^\s*(?:#|\/{2,})(.*)\n?$/; + const ExecutionComment = /^\s*#!/; + const ClosingSingleQuote = /^([^']*)'/; + const ClosingDoubleQuote = /^((?:[^\\"]|\\.)*)"/; + const EscapedQuotes = /^((?:[^\\"']|\\.)+)/; + + const LooksLikeCurl = /^\s*curl\s+/; + const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/; + + const HasProtocol = /[\s"']https?:\/\//; + const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/; + const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/; + const CurlData = /^.+\s(--data|-d)\s*/; + const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/; + + if (lines.length > 0 && ExecutionComment.test(lines[0])) { + lines.shift(); + } + + function nextLine() { + if (line.length > 0) { + return true; + } + if (lines.length === 0) { + return false; + } + line = lines.shift()!.replace(/[\r\n]+/g, '\n') + '\n'; + return true; + } + + function unescapeLastBodyEl() { + const str = body.pop().replace(/\\([\\"'])/g, '$1'); + body.push(str); + } + + // Is the next char a single or double quote? + // If so remove it + function detectQuote() { + if (line.substr(0, 1) === "'") { + line = line.substr(1); + state = 'SINGLE_QUOTE'; + } else if (line.substr(0, 1) === '"') { + line = line.substr(1); + state = 'DOUBLE_QUOTE'; + } else { + state = 'UNQUOTED'; + } + } + + // Body is finished - append to output with final LF + function addBodyToOut() { + if (body.length > 0) { + out.push(body.join('')); + body = []; + } + state = 'LF'; + out.push('\n'); + } + + // If the pattern matches, then the state is about to change, + // so add the capture to the body and detect the next state + // Otherwise add the whole line + function consumeMatching(pattern: string | RegExp) { + const result = line.match(pattern); + if (result) { + body.push(result[1]); + line = line.substr(result[0].length); + detectQuote(); + } else { + body.push(line); + line = ''; + } + } + + function parseCurlLine() { + let verb = 'GET'; + let request = ''; + let result; + if ((result = line.match(CurlVerb))) { + verb = result[1]; + } + + // JS regexen don't support possessive quantifiers, so + // we need two distinct patterns + const pattern = HasProtocol.test(line) ? CurlRequestWithProto : CurlRequestWithoutProto; + + if ((result = line.match(pattern))) { + request = result[1]; + } + + out.push(verb + ' /' + request + '\n'); + + if ((result = line.match(CurlData))) { + line = line.substr(result[0].length); + detectQuote(); + if (EmptyLine.test(line)) { + line = ''; + } + } else { + state = 'NONE'; + line = ''; + out.push(''); + } + } + + while (nextLine()) { + if (state === 'SINGLE_QUOTE') { + consumeMatching(ClosingSingleQuote); + } else if (state === 'DOUBLE_QUOTE') { + consumeMatching(ClosingDoubleQuote); + unescapeLastBodyEl(); + } else if (state === 'UNQUOTED') { + consumeMatching(EscapedQuotes); + if (body.length) { + unescapeLastBodyEl(); + } + if (state === 'UNQUOTED') { + addBodyToOut(); + line = ''; + } + } + + // the BODY state (used to match the body of a Sense request) + // can be terminated early if it encounters + // a comment or an empty line + else if (state === 'BODY') { + if (Comment.test(line) || EmptyLine.test(line)) { + addBodyToOut(); + } else { + body.push(line); + line = ''; + } + } else if (EmptyLine.test(line)) { + if (state !== 'LF') { + out.push('\n'); + state = 'LF'; + } + line = ''; + } else if ((matches = line.match(Comment))) { + out.push('#' + matches[1] + '\n'); + state = 'NONE'; + line = ''; + } else if (LooksLikeCurl.test(line)) { + parseCurlLine(); + } else if ((matches = line.match(SenseLine))) { + out.push(matches[1] + ' /' + matches[2] + '\n'); + line = ''; + state = 'BODY'; + } + + // Nothing else matches, so output with a prefix of ### for debugging purposes + else { + out.push('### ' + line); + line = ''; + } + } + + addBodyToOut(); + return out.join('').trim(); +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts new file mode 100644 index 00000000000000..9310de2724fbef --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './create'; +export * from '../legacy_core_editor/create_readonly'; +export { MODE } from '../../../lib/row_parser'; +export { SenseEditor } from './sense_editor'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.ts new file mode 100644 index 00000000000000..8df9bb8ef9a0b8 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.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. + */ +/* eslint no-undef: 0 */ + +import '../legacy_core_editor/legacy_core_editor.test.mocks'; + +// TODO: Remove this mock +jest.mock('../../../application', () => ({ legacyBackDoorToSettings: () => {} })); + +import jQuery from 'jquery'; +jest.spyOn(jQuery, 'ajax').mockImplementation( + () => + new Promise(() => { + // never resolve + }) as any +); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts new file mode 100644 index 00000000000000..9679eaa2884ce2 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts @@ -0,0 +1,502 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 RowParser from '../../../lib/row_parser'; +import * as utils from '../../../lib/utils/utils'; +// @ts-ignore +import * as es from '../../../lib/es/es'; + +import { CoreEditor, Position, Range } from '../../../types'; +import { createTokenIterator } from '../../factories'; + +import Autocomplete from '../../../lib/autocomplete/autocomplete'; + +export class SenseEditor { + currentReqRange: (Range & { markerRef: any }) | null; + parser: any; + + // @ts-ignore + private readonly autocomplete: any; + + constructor(private readonly coreEditor: CoreEditor) { + this.currentReqRange = null; + this.parser = new RowParser(this.coreEditor); + this.autocomplete = new (Autocomplete as any)({ + coreEditor, + parser: this.parser, + }); + this.coreEditor.on( + 'tokenizerUpdate', + this.highlightCurrentRequestsAndUpdateActionBar.bind(this) + ); + this.coreEditor.on('changeCursor', this.highlightCurrentRequestsAndUpdateActionBar.bind(this)); + this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this)); + } + + prevRequestStart = (rowOrPos?: number | Position): Position => { + let curRow: number; + + if (rowOrPos == null) { + curRow = this.coreEditor.getCurrentPosition().lineNumber; + } else if (_.isObject(rowOrPos)) { + curRow = (rowOrPos as Position).lineNumber; + } else { + curRow = rowOrPos as number; + } + + while (curRow > 0 && !this.parser.isStartRequestRow(curRow, this.coreEditor)) curRow--; + + return { + lineNumber: curRow, + column: 1, + }; + }; + + nextRequestStart = (rowOrPos?: number | Position) => { + let curRow: number; + if (rowOrPos == null) { + curRow = this.coreEditor.getCurrentPosition().lineNumber; + } else if (_.isObject(rowOrPos)) { + curRow = (rowOrPos as Position).lineNumber; + } else { + curRow = rowOrPos as number; + } + const maxLines = this.coreEditor.getValue().split('\n').length; + for (; curRow < maxLines - 1; curRow++) { + if (this.parser.isStartRequestRow(curRow, this.coreEditor)) { + break; + } + } + return { + row: curRow, + column: 0, + }; + }; + + autoIndent = _.debounce(async () => { + await this.coreEditor.waitForLatestTokens(); + const reqRange = await this.getRequestRange(); + if (!reqRange) { + return; + } + const parsedReq = await this.getRequest(); + + if (!parsedReq) { + return; + } + + if (parsedReq.data && parsedReq.data.length > 0) { + let indent = parsedReq.data.length === 1; // unindent multi docs by default + let formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent); + if (!formattedData.changed) { + // toggle. + indent = !indent; + formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent); + } + parsedReq.data = formattedData.data; + + this.replaceRequestRange(parsedReq, reqRange); + } + }, 25); + + update = async (data: string, reTokenizeAll = false) => { + return this.coreEditor.setValue(data, reTokenizeAll); + }; + + replaceRequestRange = (newRequest: any, requestRange: Range) => { + const text = utils.textFromRequest(newRequest); + if (requestRange) { + this.coreEditor.replaceRange(requestRange, text); + } else { + // just insert where we are + this.coreEditor.insert(this.coreEditor.getCurrentPosition(), text); + } + }; + + getRequestRange = async (lineNumber?: number): Promise => { + await this.coreEditor.waitForLatestTokens(); + + if (this.parser.isInBetweenRequestsRow(lineNumber)) { + return null; + } + + const reqStart = this.prevRequestStart(lineNumber); + const reqEnd = this.nextRequestEnd(reqStart); + + return { + start: { + ...reqStart, + }, + end: { + ...reqEnd, + }, + }; + }; + + expandRangeToRequestEdges = async ( + range = this.coreEditor.getSelectionRange() + ): Promise => { + await this.coreEditor.waitForLatestTokens(); + + let startLineNumber = range.start.lineNumber; + let endLineNumber = range.end.lineNumber; + const maxLine = Math.max(1, this.coreEditor.getLineCount()); + + if (this.parser.isInBetweenRequestsRow(startLineNumber)) { + /* Do nothing... */ + } else { + for (; startLineNumber >= 1; startLineNumber--) { + if (this.parser.isStartRequestRow(startLineNumber)) { + break; + } + } + } + + if (startLineNumber < 1 || startLineNumber > endLineNumber) { + return null; + } + // move end row to the previous request end if between requests, otherwise walk forward + if (this.parser.isInBetweenRequestsRow(endLineNumber)) { + for (; endLineNumber >= startLineNumber; endLineNumber--) { + if (this.parser.isEndRequestRow(endLineNumber)) { + break; + } + } + } else { + for (; endLineNumber <= maxLine; endLineNumber++) { + if (this.parser.isEndRequestRow(endLineNumber)) { + break; + } + } + } + + if (endLineNumber < startLineNumber || endLineNumber > maxLine) { + return null; + } + + const endColumn = + (this.coreEditor.getLineValue(endLineNumber) || '').replace(/\s+$/, '').length + 1; + return { + start: { + lineNumber: startLineNumber, + column: 1, + }, + end: { + lineNumber: endLineNumber, + column: endColumn, + }, + }; + }; + + getRequestInRange = async (range?: Range) => { + await this.coreEditor.waitForLatestTokens(); + if (!range) { + return null; + } + const request: { + method: string; + data: string[]; + url: string | null; + range: Range; + } = { + method: '', + data: [], + url: null, + range, + }; + + const pos = range.start; + const tokenIter = createTokenIterator({ editor: this.coreEditor, position: pos }); + let t = tokenIter.getCurrentToken(); + if (this.parser.isEmptyToken(t)) { + // if the row starts with some spaces, skip them. + t = this.parser.nextNonEmptyToken(tokenIter); + } + if (t == null) { + return null; + } + + request.method = t.value; + t = this.parser.nextNonEmptyToken(tokenIter); + + if (!t || t.type === 'method') { + return null; + } + + request.url = ''; + + while (t && t.type && t.type.indexOf('url') === 0) { + request.url += t.value; + t = tokenIter.stepForward(); + } + if (this.parser.isEmptyToken(t)) { + // if the url row ends with some spaces, skip them. + t = this.parser.nextNonEmptyToken(tokenIter); + } + let bodyStartLineNumber = (t ? 0 : 1) + tokenIter.getCurrentPosition().lineNumber; // artificially increase end of docs. + let dataEndPos: Position; + while ( + bodyStartLineNumber < range.end.lineNumber || + (bodyStartLineNumber === range.end.lineNumber && 1 < range.end.column) + ) { + dataEndPos = this.nextDataDocEnd({ + lineNumber: bodyStartLineNumber, + column: 1, + }); + const bodyRange: Range = { + start: { + lineNumber: bodyStartLineNumber, + column: 1, + }, + end: dataEndPos, + }; + const data = this.coreEditor.getValueInRange(bodyRange)!; + request.data.push(data.trim()); + bodyStartLineNumber = dataEndPos.lineNumber + 1; + } + + return request; + }; + + getRequestsInRange = async ( + range = this.coreEditor.getSelectionRange(), + includeNonRequestBlocks = false + ): Promise => { + await this.coreEditor.waitForLatestTokens(); + if (!range) { + return []; + } + + const expandedRange = await this.expandRangeToRequestEdges(range); + + if (!expandedRange) { + return []; + } + + const requests: any = []; + + let rangeStartCursor = expandedRange.start.lineNumber; + const endLineNumber = expandedRange.end.lineNumber; + + // move to the next request start (during the second iterations this may not be exactly on a request + let currentLineNumber = expandedRange.start.lineNumber; + + const flushNonRequestBlock = () => { + if (includeNonRequestBlocks) { + const nonRequestPrefixBlock = this.coreEditor + .getLines(rangeStartCursor, currentLineNumber - 1) + .join('\n'); + if (nonRequestPrefixBlock) { + requests.push(nonRequestPrefixBlock); + } + } + }; + + while (currentLineNumber <= endLineNumber) { + if (this.parser.isStartRequestRow(currentLineNumber)) { + flushNonRequestBlock(); + const request = await this.getRequest(currentLineNumber); + if (!request) { + // Something has probably gone wrong. + return requests; + } else { + requests.push(request); + rangeStartCursor = currentLineNumber = request.range.end.lineNumber + 1; + } + } else { + ++currentLineNumber; + } + } + + flushNonRequestBlock(); + + return requests; + }; + + getRequest = async (row?: number) => { + await this.coreEditor.waitForLatestTokens(); + if (this.parser.isInBetweenRequestsRow(row)) { + return null; + } + + const range = await this.getRequestRange(row); + return this.getRequestInRange(range!); + }; + + moveToPreviousRequestEdge = async () => { + await this.coreEditor.waitForLatestTokens(); + const pos = this.coreEditor.getCurrentPosition(); + for ( + pos.lineNumber--; + pos.lineNumber > 1 && !this.parser.isRequestEdge(pos.lineNumber); + pos.lineNumber-- + ) { + // loop for side effects + } + this.coreEditor.moveCursorToPosition({ + lineNumber: pos.lineNumber, + column: 1, + }); + }; + + moveToNextRequestEdge = async (moveOnlyIfNotOnEdge: boolean) => { + await this.coreEditor.waitForLatestTokens(); + const pos = this.coreEditor.getCurrentPosition(); + const maxRow = this.coreEditor.getLineCount(); + if (!moveOnlyIfNotOnEdge) { + pos.lineNumber++; + } + for ( + ; + pos.lineNumber < maxRow && !this.parser.isRequestEdge(pos.lineNumber); + pos.lineNumber++ + ) { + // loop for side effects + } + this.coreEditor.moveCursorToPosition({ + lineNumber: pos.lineNumber, + column: 1, + }); + }; + + nextRequestEnd = (pos: Position): Position => { + pos = pos || this.coreEditor.getCurrentPosition(); + const maxLines = this.coreEditor.getLineCount(); + let curLineNumber = pos.lineNumber; + for (; curLineNumber <= maxLines; ++curLineNumber) { + const curRowMode = this.parser.getRowParseMode(curLineNumber); + // eslint-disable-next-line no-bitwise + if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) { + break; + } + // eslint-disable-next-line no-bitwise + if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) { + break; + } + } + + const column = + (this.coreEditor.getLineValue(curLineNumber) || '').replace(/\s+$/, '').length + 1; + + return { + lineNumber: curLineNumber, + column, + }; + }; + + nextDataDocEnd = (pos: Position): Position => { + pos = pos || this.coreEditor.getCurrentPosition(); + let curLineNumber = pos.lineNumber; + const maxLines = this.coreEditor.getLineCount(); + for (; curLineNumber < maxLines; curLineNumber++) { + const curRowMode = this.parser.getRowParseMode(curLineNumber); + // eslint-disable-next-line no-bitwise + if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) { + break; + } + // eslint-disable-next-line no-bitwise + if ((curRowMode & this.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) { + break; + } + // eslint-disable-next-line no-bitwise + if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) { + break; + } + } + + const column = + (this.coreEditor.getLineValue(curLineNumber) || '').length + + 1 /* Range goes to 1 after last char */; + + return { + lineNumber: curLineNumber, + column, + }; + }; + + highlightCurrentRequestsAndUpdateActionBar = _.debounce(async () => { + await this.coreEditor.waitForLatestTokens(); + const expandedRange = await this.expandRangeToRequestEdges(); + if (expandedRange === null && this.currentReqRange === null) { + return; + } + if ( + expandedRange !== null && + this.currentReqRange !== null && + expandedRange.start.lineNumber === this.currentReqRange.start.lineNumber && + expandedRange.end.lineNumber === this.currentReqRange.end.lineNumber + ) { + // same request, now see if we are on the first line and update the action bar + const cursorLineNumber = this.coreEditor.getCurrentPosition().lineNumber; + if (cursorLineNumber === this.currentReqRange.start.lineNumber) { + this.updateActionsBar(); + } + return; // nothing to do.. + } + + if (this.currentReqRange) { + this.coreEditor.removeMarker(this.currentReqRange.markerRef); + } + + this.currentReqRange = expandedRange as any; + if (this.currentReqRange) { + this.currentReqRange.markerRef = this.coreEditor.addMarker(this.currentReqRange); + } + this.updateActionsBar(); + }, 25); + + getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise => { + const requests = await this.getRequestsInRange(range, true); + const result = _.map(requests, req => { + if (typeof req === 'string') { + // no request block + return req; + } + + const esPath = req.url; + const esMethod = req.method; + const esData = req.data; + + // this is the first url defined in elasticsearch.hosts + const url = es.constructESUrl(elasticsearchBaseUrl, esPath); + + let ret = 'curl -X' + esMethod + ' "' + url + '"'; + if (esData && esData.length) { + ret += " -H 'Content-Type: application/json' -d'\n"; + const dataAsString = utils.collapseLiteralStrings(esData.join('\n')); + // since Sense doesn't allow single quote json string any single qoute is within a string. + ret += dataAsString.replace(/'/g, '\\"'); + if (esData.length > 1) { + ret += '\n'; + } // end with a new line + ret += "'"; + } + return ret; + }); + + return result.join('\n'); + }; + + updateActionsBar = () => this.coreEditor.legacyUpdateUI(this.currentReqRange); + + getCoreEditor() { + return this.coreEditor; + } +} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/stores/editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/stores/editor.ts rename to src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/stores/request.ts b/src/legacy/core_plugins/console/public/np_ready/application/stores/request.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/stores/request.ts rename to src/legacy/core_plugins/console/public/np_ready/application/stores/request.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/_app.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/_app.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss index b19fd438f8ee3e..159e9f9e8a1730 100644 --- a/src/legacy/core_plugins/console/public/quarantined/_app.scss +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss @@ -33,6 +33,7 @@ flex: 1 1 1px; } +.conApp__textAreaLabelHack, .conApp__editorContent, .conApp__outputContent { height: 100%; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/directives/_help.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_help.scss similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/directives/_help.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/components/_help.scss diff --git a/src/legacy/core_plugins/console/public/quarantined/src/directives/_history.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_history.scss similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/directives/_history.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/components/_history.scss diff --git a/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss new file mode 100644 index 00000000000000..9dfef202d1254b --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss @@ -0,0 +1,2 @@ +@import 'help'; +@import 'history'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/styles/index.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/index.scss new file mode 100644 index 00000000000000..dc45f6cfdacf5c --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/index.scss @@ -0,0 +1,11 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +// Prefix all styles with "con" to avoid conflicts. +// Examples +// conChart +// conChart__legend +// conChart__legend--small +// conChart__legend-isLoading + +@import 'app'; +@import 'components/index'; diff --git a/src/legacy/core_plugins/console/public/np_ready/index.ts b/src/legacy/core_plugins/console/public/np_ready/index.ts new file mode 100644 index 00000000000000..045420f401e3ba --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/index.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 { PluginInitializerContext } from 'kibana/public'; + +import { ConsoleUIPlugin } from './plugin'; + +export { ConsoleUIPlugin as Plugin }; + +export function plugin(ctx: PluginInitializerContext) { + return new ConsoleUIPlugin(ctx); +} diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/index.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.test.ts b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.test.ts similarity index 84% rename from src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.test.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.test.ts index aecb6cb37ee812..00bfe32c859063 100644 --- a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.test.ts +++ b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.test.ts @@ -17,15 +17,17 @@ * under the License. */ -// @ts-ignore -import '../../../../public/quarantined/tests/src/setup_mocks'; +import '../../application/models/sense_editor/sense_editor.test.mocks'; -import { Editor } from 'brace'; import $ from 'jquery'; -// @ts-ignore -import { initializeEditor } from '../../../../public/quarantined/src/input.ts'; -import { AceTokensProvider } from '.'; +// TODO: +// We import from application models as a convenient way to bootstrap loading up of an editor using +// this lib. We also need to import application specific mocks which is not ideal. +// In this situation, the token provider lib knows about app models in tests, which it really shouldn't. Should create +// a better sandbox in future. +import { create, SenseEditor } from '../../application/models/sense_editor'; + import { Position, Token, TokensProvider } from '../../types'; interface RunTestArgs { @@ -34,7 +36,7 @@ interface RunTestArgs { } describe('Ace (legacy) token provider', () => { - let aceEditor: Editor & { $el: any; autocomplete: any; update: any }; + let senseEditor: SenseEditor; let tokenProvider: TokensProvider; beforeEach(() => { // Set up our document body @@ -44,16 +46,18 @@ describe('Ace (legacy) token provider', () => {
`; - aceEditor = initializeEditor($('#ConAppEditor'), $('#ConAppEditorActions')); + senseEditor = create(document.querySelector('#ConAppEditor')!); + + $(senseEditor.getCoreEditor().getContainer())!.show(); - aceEditor.$el.show(); - aceEditor.autocomplete._test.removeChangeListener(); - tokenProvider = new AceTokensProvider(aceEditor.session); + (senseEditor as any).autocomplete._test.removeChangeListener(); + tokenProvider = senseEditor.getCoreEditor().getTokenProvider(); }); - afterEach(done => { - aceEditor.$el.hide(); - aceEditor.autocomplete._test.addChangeListener(); - aceEditor.update('', done); + + afterEach(async () => { + $(senseEditor.getCoreEditor().getContainer())!.hide(); + (senseEditor as any).autocomplete._test.addChangeListener(); + await senseEditor.update('', true); }); describe('#getTokens', () => { @@ -63,7 +67,7 @@ describe('Ace (legacy) token provider', () => { done, lineNumber = 1, }: RunTestArgs & { expectedTokens: Token[] | null; lineNumber?: number }) => { - aceEditor.update(input, function() { + senseEditor.update(input, true).then(() => { const tokens = tokenProvider.getTokens(lineNumber); expect(tokens).toEqual(expectedTokens); if (done) done(); @@ -160,7 +164,7 @@ describe('Ace (legacy) token provider', () => { test('case 2 - empty lines', done => { runTest({ input: `GET http://test:user@somehost/ - + @@ -182,7 +186,7 @@ describe('Ace (legacy) token provider', () => { done, position, }: RunTestArgs & { expectedToken: Token | null; position: Position }) => { - aceEditor.update(input, function() { + senseEditor.update(input, true).then(() => { const tokens = tokenProvider.getTokenAt(position); expect(tokens).toEqual(expectedToken); if (done) done(); diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.ts b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_autocomplete.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_autocomplete.test.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/url_autocomplete.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_autocomplete.test.js index 154a7e9ba2b4ba..77c211a71d9867 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_autocomplete.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_autocomplete.test.js @@ -16,18 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; +import '../../../application/models/sense_editor/sense_editor.test.mocks'; + const _ = require('lodash'); import { URL_PATH_END_MARKER, UrlPatternMatcher, ListComponent -} from '../../src/autocomplete/components'; +} from '../../autocomplete/components'; -import { populateContext } from '../../src/autocomplete/engine'; +import { populateContext } from '../../autocomplete/engine'; describe('Url autocomplete', () => { function patternsTest( diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_params.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_params.test.js similarity index 94% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/url_params.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_params.test.js index 7b44d91fa503a3..b91c463bb14ff7 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_params.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_params.test.js @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; +import '../../../application/models/sense_editor/sense_editor.test.mocks'; import 'brace'; import 'brace/mode/javascript'; import 'brace/mode/json'; const _ = require('lodash'); -import { UrlParams } from '../../src/autocomplete/url_params'; -import { populateContext } from '../../src/autocomplete/engine'; +import { UrlParams } from '../../autocomplete/url_params'; +import { populateContext } from '../../autocomplete/engine'; describe('Url params', () => { function paramTest( diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts similarity index 94% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts index 47edf42f0eec55..8edb26f7817e4f 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import ace, { Editor as AceEditor, IEditSession, Position as AcePosition } from 'brace'; +import ace, { Editor as AceEditor, IEditSession } from 'brace'; import { i18n } from '@kbn/i18n'; import { @@ -27,17 +27,18 @@ import { getGlobalAutocompleteComponents, getUnmatchedEndpointComponents, // @ts-ignore -} from './kb'; -// @ts-ignore -import utils from './utils'; +} from '../kb/kb'; + +import * as utils from '../utils/utils'; + // @ts-ignore -import { populateContext } from './autocomplete/engine'; +import { populateContext } from './engine'; // @ts-ignore -import { URL_PATH_END_MARKER } from './autocomplete/components'; -import { createTokenIterator } from '../../../np_ready/public/application/factories'; +import { URL_PATH_END_MARKER } from './components/index'; +import { createTokenIterator } from '../../application/factories'; -import { Position, Token, Range } from '../../../np_ready/public/types'; -import { LegacyEditor } from '../../../np_ready/public/application/models'; +import { Position, Token, Range, CoreEditor } from '../../types'; +import { SenseEditor } from '../../application/models/sense_editor'; let LAST_EVALUATED_TOKEN: any = null; @@ -54,7 +55,7 @@ function isUrlParamsToken(token: any) { } } function getCurrentMethodAndTokenPaths( - editor: LegacyEditor, + editor: CoreEditor, pos: Position, parser: any, forceEndOfUrl?: boolean @@ -297,12 +298,12 @@ function getCurrentMethodAndTokenPaths( } return ret; } -export function getEndpointFromPosition(aceEditor: AceEditor, pos: AcePosition, parser: any) { - const editor = new LegacyEditor(aceEditor); +export function getEndpointFromPosition(senseEditor: SenseEditor, pos: Position, parser: any) { + const editor = senseEditor.getCoreEditor(); const context = { ...getCurrentMethodAndTokenPaths( editor, - { column: pos.column + 1, lineNumber: pos.row + 1 }, + { column: pos.column, lineNumber: pos.lineNumber }, parser, true ), @@ -313,23 +314,7 @@ export function getEndpointFromPosition(aceEditor: AceEditor, pos: AcePosition, } // eslint-disable-next-line -export default function({ - coreEditor: editor, - parser, - execCommand, - getCursorPosition, - isCompleterActive, - addChangeListener, - removeChangeListener, -}: { - coreEditor: LegacyEditor; - parser: any; - execCommand: (cmd: string) => void; - getCursorPosition: () => Position | null; - isCompleterActive: () => boolean; - addChangeListener: (fn: any) => void; - removeChangeListener: (fn: any) => void; -}) { +export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor; parser: any }) { function isUrlPathToken(token: Token | null) { switch ((token || ({} as any)).type) { case 'url.slash': @@ -404,7 +389,7 @@ export default function({ valueToInsert = context.prefixToAdd + valueToInsert + context.suffixToAdd; // disable listening to the changes we are making. - removeChangeListener(editorChangeListener); + editor.off('changeSelection', editorChangeListener); if (context.rangeToReplace.start.column !== context.rangeToReplace.end.column) { editor.replace(context.rangeToReplace, valueToInsert); @@ -456,10 +441,10 @@ export default function({ } // re-enable listening to typing - addChangeListener(editorChangeListener); + editor.on('changeSelection', editorChangeListener); } - function getAutoCompleteContext(ctxEditor: LegacyEditor, pos: Position) { + function getAutoCompleteContext(ctxEditor: CoreEditor, pos: Position) { // deduces all the parameters need to position and insert the auto complete const context: any = { autoCompleteSet: null, // instructions for what can be here @@ -567,10 +552,8 @@ export default function({ // in between request on an empty if (editor.getLineValue(pos.lineNumber).trim() === '') { - // check if the previous line is a single line begging of a new request - rowMode = parser.getRowParseMode( - pos.lineNumber - 1 - 1 /* see RowParser for why the added -1, for now */ - ); + // check if the previous line is a single line beginning of a new request + rowMode = parser.getRowParseMode(pos.lineNumber - 1); // eslint-disable-next-line no-bitwise if ( // eslint-disable-next-line no-bitwise @@ -964,23 +947,23 @@ export default function({ } LAST_EVALUATED_TOKEN = currentToken; - execCommand('startAutocomplete'); + editor.execCommand('startAutocomplete'); }, 100); function editorChangeListener() { - const position = getCursorPosition(); - if (position && !isCompleterActive()) { + const position = editor.getCurrentPosition(); + if (position && !editor.isCompleterActive()) { evaluateCurrentTokenAfterAChange(position); } } function getCompletions( DO_NOT_USE: AceEditor, - session: IEditSession, - pos: any, - prefix: any, - callback: any + DO_NOT_USE_SESSION: IEditSession, + pos: { row: number; column: number }, + prefix: string, + callback: (...args: any[]) => void ) { const position: Position = { lineNumber: pos.row + 1, @@ -1054,7 +1037,7 @@ export default function({ } } - addChangeListener(editorChangeListener); + editor.on('changeSelection', editorChangeListener); // Hook into Ace @@ -1090,8 +1073,8 @@ export default function({ _test: { getCompletions, addReplacementInfoToContext, - addChangeListener: () => addChangeListener(editorChangeListener), - removeChangeListener: () => removeChangeListener(editorChangeListener), + addChangeListener: () => editor.on('changeSelection', editorChangeListener), + removeChangeListener: () => editor.off('changeSelection', editorChangeListener), }, }; } diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/body_completer.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/body_completer.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/accept_endpoint_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/accept_endpoint_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/accept_endpoint_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/accept_endpoint_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/autocomplete_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/autocomplete_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/conditional_proxy.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/conditional_proxy.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/conditional_proxy.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/conditional_proxy.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/constant_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/constant_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/constant_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/constant_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/field_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/field_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/field_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/field_autocomplete_component.js index b2424bebf1b9de..e07db78c4cca91 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/field_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/field_autocomplete_component.js @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; function FieldGenerator(context) { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/global_only_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/global_only_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/global_only_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/global_only_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/id_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/id_autocomplete_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/id_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/id_autocomplete_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index_autocomplete_component.js index 33e27852caff2a..03d67c9e27ee87 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index_autocomplete_component.js @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; function nonValidIndexType(token) { return !(token === '_all' || token[0] !== '_'); diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/list_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/list_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/list_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/list_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/object_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/object_component.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/object_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/object_component.js index 4db392e60ff832..5cff4a15647ce1 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/object_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/object_component.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { SharedComponent } from '.'; +import { SharedComponent } from './index'; /** * @param constants list of components that represent constant keys * @param patternsAndWildCards list of components that represent patterns and should be matched only if diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/shared_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/shared_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/shared_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/shared_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/simple_param_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/simple_param_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/simple_param_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/simple_param_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/template_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/template_autocomplete_component.js similarity index 95% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/template_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/template_autocomplete_component.js index 0c00b2f93ee6f9..cc62a2f9eeea6c 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/template_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/template_autocomplete_component.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; export class TemplateAutocompleteComponent extends ListComponent { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/type_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/type_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/type_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/type_autocomplete_component.js index 4f1c85213b689a..ca317fec9e27f2 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/type_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/type_autocomplete_component.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; import { ListComponent } from './list_component'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; function TypeGenerator(context) { return mappings.getTypes(context.indices); } diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/url_pattern_matcher.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js similarity index 99% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/url_pattern_matcher.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js index 20978c2fbea607..dfae1382bed9bb 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/url_pattern_matcher.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js @@ -23,7 +23,7 @@ import { AcceptEndpointComponent, ListComponent, SimpleParamComponent, -} from '.'; +} from './index'; /** * @param parametrizedComponentFactories a dict of the following structure diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/username_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/username_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/username_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/username_autocomplete_component.js index da3c63b69c610c..26b7bd5c48c999 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/username_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/username_autocomplete_component.js @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; function nonValidUsernameType(token) { return token[0] === '_'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/engine.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/engine.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/engine.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/engine.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/url_params.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/url_params.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/url_params.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/url_params.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.test.js similarity index 97% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.test.js index e1bed169bb7301..bf783b3dfe8aea 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.test.js @@ -18,7 +18,7 @@ */ const _ = require('lodash'); -const curl = require('../../src/curl'); +const curl = require('../curl'); import curlTests from './curl_parsing.txt'; describe('CURL', () => { diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.txt b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.txt similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.txt rename to src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.txt diff --git a/src/legacy/core_plugins/console/public/quarantined/src/curl.js b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/curl.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/curl.js rename to src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/curl.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/content_type.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/es/__tests__/content_type.test.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/content_type.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/es/__tests__/content_type.test.js index 224eab7dc925d8..acc33e331c9f97 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/content_type.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/es/__tests__/content_type.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { getContentType } from '../../src/es'; +import { getContentType } from '../es'; const APPLICATION_JSON = 'application/json'; describe('Content type', () => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/es.js b/src/legacy/core_plugins/console/public/np_ready/lib/es/es.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/es.js rename to src/legacy/core_plugins/console/public/np_ready/lib/es/es.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/kb.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/__tests__/kb.test.js similarity index 95% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/kb.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/__tests__/kb.test.js index 1618573f911edc..ad29f9808a6c23 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/kb.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/kb/__tests__/kb.test.js @@ -18,14 +18,11 @@ */ import _ from 'lodash'; -import { populateContext } from '../../src/autocomplete/engine'; - -import './setup_mocks'; -import 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; -const kb = require('../../src/kb'); -const mappings = require('../../src/mappings'); +import { populateContext } from '../../autocomplete/engine'; + +import '../../../application/models/sense_editor/sense_editor.test.mocks'; +const kb = require('../../kb'); +const mappings = require('../../mappings/mappings'); describe('Knowledge base', () => { beforeEach(() => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/kb/api.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/api.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/kb/api.js rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/api.js diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/kb/index.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/index.js new file mode 100644 index 00000000000000..383ebef57da920 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/kb/index.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. + */ + +export * from './kb'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/kb.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/kb.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/src/kb.js rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/kb.js index 6a7edf5bc337ff..ffba14fad3f379 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/kb.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/kb/kb.js @@ -24,12 +24,12 @@ import { ListComponent, TemplateAutocompleteComponent, UsernameAutocompleteComponent, -} from './autocomplete/components'; +} from '../autocomplete/components'; import $ from 'jquery'; import _ from 'lodash'; -import Api from './kb/api'; +import Api from './api'; let ACTIVE_API = new Api(); const isNotAnIndexName = name => name[0] === '_' && name !== '_all'; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/mapping.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/__tests__/mapping.test.js similarity index 97% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/mapping.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/mappings/__tests__/mapping.test.js index d79f3c50b83735..875a16402e3945 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/mapping.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/__tests__/mapping.test.js @@ -16,11 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; -const mappings = require('../../src/mappings'); +import '../../../application/models/sense_editor/sense_editor.test.mocks'; +const mappings = require('../mappings'); describe('Mappings', () => { beforeEach(() => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/mappings.js b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/mappings.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/src/mappings.js rename to src/legacy/core_plugins/console/public/np_ready/lib/mappings/mappings.js index 69f122a6078362..b0acf369260e90 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/mappings.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/mappings.js @@ -17,11 +17,11 @@ * under the License. */ -import { legacyBackDoorToSettings } from '../../../np_ready/public/application'; +import { legacyBackDoorToSettings } from '../../application'; const $ = require('jquery'); const _ = require('lodash'); -const es = require('./es'); +const es = require('../es/es'); // NOTE: If this value ever changes to be a few seconds or less, it might introduce flakiness // due to timing issues in our app.js tests. diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts b/src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts new file mode 100644 index 00000000000000..b56d15e1788109 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/row_parser.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. + */ + +import { CoreEditor, Token } from '../types'; +import { TokenIterator } from './token_iterator'; + +export const MODE = { + REQUEST_START: 2, + IN_REQUEST: 4, + MULTI_DOC_CUR_DOC_END: 8, + REQUEST_END: 16, + BETWEEN_REQUESTS: 32, +}; + +// eslint-disable-next-line import/no-default-export +export default class RowParser { + constructor(private readonly editor: CoreEditor) {} + + MODE = MODE; + + getRowParseMode(lineNumber = this.editor.getCurrentPosition().lineNumber) { + const linesCount = this.editor.getLineCount(); + if (lineNumber > linesCount || lineNumber < 1) { + return MODE.BETWEEN_REQUESTS; + } + const mode = this.editor.getLineState(lineNumber); + if (!mode) { + return MODE.BETWEEN_REQUESTS; + } // shouldn't really happen + + if (mode !== 'start') { + return MODE.IN_REQUEST; + } + let line = (this.editor.getLineValue(lineNumber) || '').trim(); + if (!line || line[0] === '#') { + return MODE.BETWEEN_REQUESTS; + } // empty line or a comment waiting for a new req to start + + if (line.indexOf('}', line.length - 1) >= 0) { + // check for a multi doc request (must start a new json doc immediately after this one end. + lineNumber++; + if (lineNumber < linesCount + 1) { + line = (this.editor.getLineValue(lineNumber) || '').trim(); + if (line.indexOf('{') === 0) { + // next line is another doc in a multi doc + // eslint-disable-next-line no-bitwise + return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST; + } + } + // eslint-disable-next-line no-bitwise + return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request + } + + // check for single line requests + lineNumber++; + if (lineNumber >= linesCount + 1) { + // eslint-disable-next-line no-bitwise + return MODE.REQUEST_START | MODE.REQUEST_END; + } + line = (this.editor.getLineValue(lineNumber) || '').trim(); + if (line.indexOf('{') !== 0) { + // next line is another request + // eslint-disable-next-line no-bitwise + return MODE.REQUEST_START | MODE.REQUEST_END; + } + + return MODE.REQUEST_START; + } + + rowPredicate(lineNumber: number | undefined, editor: CoreEditor, value: any) { + const mode = this.getRowParseMode(lineNumber); + // eslint-disable-next-line no-bitwise + return (mode & value) > 0; + } + + isEndRequestRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.REQUEST_END); + } + + isRequestEdge(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + // eslint-disable-next-line no-bitwise + return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START); + } + + isStartRequestRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.REQUEST_START); + } + + isInBetweenRequestsRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS); + } + + isInRequestsRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.IN_REQUEST); + } + + isMultiDocDocEndRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END); + } + + isEmptyToken(tokenOrTokenIter: TokenIterator | Token | null) { + const token = + tokenOrTokenIter && (tokenOrTokenIter as TokenIterator).getCurrentToken + ? (tokenOrTokenIter as TokenIterator).getCurrentToken() + : tokenOrTokenIter; + return !token || (token as Token).type === 'whitespace'; + } + + isUrlOrMethodToken(tokenOrTokenIter: TokenIterator | Token) { + const t = (tokenOrTokenIter as TokenIterator)?.getCurrentToken() ?? (tokenOrTokenIter as Token); + return t && t.type && (t.type === 'method' || t.type.indexOf('url') === 0); + } + + nextNonEmptyToken(tokenIter: TokenIterator) { + let t = tokenIter.stepForward(); + while (t && this.isEmptyToken(t)) { + t = tokenIter.stepForward(); + } + return t; + } + + prevNonEmptyToken(tokenIter: TokenIterator) { + let t = tokenIter.stepBackward(); + // empty rows return null token. + while ((t || tokenIter.getCurrentPosition().lineNumber > 1) && this.isEmptyToken(t)) + t = tokenIter.stepBackward(); + return t; + } +} diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/index.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.test.ts b/src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.test.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.test.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.test.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.ts b/src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js similarity index 91% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/utils.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js index a139aa47b911fa..827beadee0f0b6 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js @@ -18,12 +18,25 @@ */ const _ = require('lodash'); -const utils = require('../../src/utils'); +const utils = require('../utils'); const collapsingTests = require('./utils_string_collapsing.txt'); const expandingTests = require('./utils_string_expanding.txt'); describe('Utils class', () => { + describe('collapseLiteralStrings', () => { + it('will collapse multiline strings', () => { + const multiline = '{ "foo": """bar\nbaz""" }'; + expect(utils.collapseLiteralStrings(multiline)).toEqual('{ "foo": "bar\\nbaz" }'); + }); + + it('will collapse multiline strings with CRLF endings', () => { + const multiline = '{ "foo": """bar\r\nbaz""" }'; + expect(utils.collapseLiteralStrings(multiline)).toEqual('{ "foo": "bar\\r\\nbaz" }'); + }); + }); + + _.each(collapsingTests.split(/^=+$/m), function (fixture) { if (fixture.trim() === '') { return; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_collapsing.txt similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_collapsing.txt diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_expanding.txt similarity index 79% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_expanding.txt index 34bf0f3bc20e72..88467ab3672cd7 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_expanding.txt @@ -40,3 +40,15 @@ Correctly parse with JSON embedded inside values "content\\\\": """ { "json": "inside\\" ok! 1 " } """ } +========== +Correctly handle new lines in triple quotes +------------------------------------- +{ + "query": "\n SELECT * FROM \"TABLE\"\n " +} +------------------------------------- +{ + "query": """ + SELECT * FROM "TABLE" + """ +} diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/utils/utils.ts b/src/legacy/core_plugins/console/public/np_ready/lib/utils/utils.ts new file mode 100644 index 00000000000000..a7f59acf1d77b6 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/utils.ts @@ -0,0 +1,136 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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'; + +export function textFromRequest(request: any) { + let data = request.data; + if (typeof data !== 'string') { + data = data.join('\n'); + } + return request.method + ' ' + request.url + '\n' + data; +} + +export function jsonToString(data: any, indent: boolean) { + return JSON.stringify(data, null, indent ? 2 : 0); +} + +export function formatRequestBodyDoc(data: string[], indent: boolean) { + let changed = false; + const formattedData = []; + for (let i = 0; i < data.length; i++) { + const curDoc = data[i]; + try { + let newDoc = jsonToString(JSON.parse(collapseLiteralStrings(curDoc)), indent); + if (indent) { + newDoc = expandLiteralStrings(newDoc); + } + changed = changed || newDoc !== curDoc; + formattedData.push(newDoc); + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + formattedData.push(curDoc); + } + } + + return { + changed, + data: formattedData, + }; +} + +export function collapseLiteralStrings(data: any) { + const splitData = data.split(`"""`); + for (let idx = 1; idx < splitData.length - 1; idx += 2) { + splitData[idx] = JSON.stringify(splitData[idx]); + } + return splitData.join(''); +} + +/* + The following regex describes global match on: + 1. one colon followed by any number of space characters + 2. one double quote (not escaped, special case for JSON in JSON). + 3. greedily match any non double quote and non newline char OR any escaped double quote char (non-capturing). + 4. handle a special case where an escaped slash may be the last character + 5. one double quote + + For instance: `: "some characters \" here"` + Will match and be expanded to: `"""some characters " here"""` + + */ + +const LITERAL_STRING_CANDIDATES = /((:[\s\r\n]*)([^\\])"(\\"|[^"\n])*\\?")/g; + +export function expandLiteralStrings(data: string) { + return data.replace(LITERAL_STRING_CANDIDATES, (match, string) => { + // Expand to triple quotes if there are _any_ slashes + if (string.match(/\\./)) { + const firstDoubleQuoteIdx = string.indexOf('"'); + const colonAndAnySpacing = string.slice(0, firstDoubleQuoteIdx); + const rawStringifiedValue = string.slice(firstDoubleQuoteIdx, string.length); + // Remove one level of JSON stringification + const jsonValue = JSON.parse(rawStringifiedValue); + return `${colonAndAnySpacing}"""${jsonValue}"""`; + } else { + return string; + } + }); +} + +export function extractDeprecationMessages(warnings: string) { + // pattern for valid warning header + const re = /\d{3} [0-9a-zA-Z!#$%&'*+-.^_`|~]+ \"((?:\t| |!|[\x23-\x5b]|[\x5d-\x7e]|[\x80-\xff]|\\\\|\\")*)\"(?: \"[^"]*\")?/; + // split on any comma that is followed by an even number of quotes + return _.map(splitOnUnquotedCommaSpace(warnings), warning => { + const match = re.exec(warning); + // extract the actual warning if there was a match + return '#! Deprecation: ' + (match !== null ? unescape(match[1]) : warning); + }); +} + +export function unescape(s: string) { + return s.replace(/\\\\/g, '\\').replace(/\\"/g, '"'); +} + +export function splitOnUnquotedCommaSpace(s: string) { + let quoted = false; + const arr = []; + let buffer = ''; + let i = 0; + while (i < s.length) { + let token = s.charAt(i++); + if (token === '\\' && i < s.length) { + token += s.charAt(i++); + } else if (token === ',' && i < s.length && s.charAt(i) === ' ') { + token += s.charAt(i++); + } + if (token === '"') { + quoted = !quoted; + } else if (!quoted && token === ', ') { + arr.push(buffer); + buffer = ''; + continue; + } + buffer += token; + } + arr.push(buffer); + return arr; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/plugin.ts b/src/legacy/core_plugins/console/public/np_ready/plugin.ts new file mode 100644 index 00000000000000..cbe262b1246772 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/plugin.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 { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; + +import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from 'src/core/public'; +import { XPluginSet } from '../legacy'; + +export class ConsoleUIPlugin implements Plugin { + // @ts-ignore + constructor(private readonly ctx: PluginInitializerContext) {} + + async setup({ notifications }: CoreSetup, pluginSet: XPluginSet) { + const { + __LEGACY: { I18nContext, elasticsearchUrl, category }, + dev_tools, + home, + } = pluginSet; + + home.featureCatalogue.register({ + id: 'console', + title: i18n.translate('console.devToolsTitle', { + defaultMessage: 'Console', + }), + description: i18n.translate('console.devToolsDescription', { + defaultMessage: 'Skip cURL and use this JSON interface to work with your data directly.', + }), + icon: 'consoleApp', + path: '/app/kibana#/dev_tools/console', + showOnHomePage: true, + category, + }); + + dev_tools.register({ + id: 'console', + order: 1, + title: i18n.translate('console.consoleDisplayName', { + defaultMessage: 'Console', + }), + enableRouting: false, + async mount(ctx, { element }) { + const { boot } = await import('./application'); + render( + boot({ + docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, + I18nContext, + notifications, + elasticsearchUrl, + }), + element + ); + return () => { + unmountComponentAtNode(element); + }; + }, + }); + } + + async start(core: CoreStart) {} +} diff --git a/src/legacy/core_plugins/console/np_ready/public/services/history.ts b/src/legacy/core_plugins/console/public/np_ready/services/history.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/history.ts rename to src/legacy/core_plugins/console/public/np_ready/services/history.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/services/index.ts b/src/legacy/core_plugins/console/public/np_ready/services/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/index.ts rename to src/legacy/core_plugins/console/public/np_ready/services/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/services/settings.ts b/src/legacy/core_plugins/console/public/np_ready/services/settings.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/settings.ts rename to src/legacy/core_plugins/console/public/np_ready/services/settings.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/services/storage.ts b/src/legacy/core_plugins/console/public/np_ready/services/storage.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/storage.ts rename to src/legacy/core_plugins/console/public/np_ready/services/storage.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/common.ts b/src/legacy/core_plugins/console/public/np_ready/types/common.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/common.ts rename to src/legacy/core_plugins/console/public/np_ready/types/common.ts diff --git a/src/legacy/core_plugins/console/public/np_ready/types/core_editor.ts b/src/legacy/core_plugins/console/public/np_ready/types/core_editor.ts new file mode 100644 index 00000000000000..8de4c78333feea --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/types/core_editor.ts @@ -0,0 +1,255 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TokensProvider } from './tokens_provider'; +import { Token } from './token'; + +type MarkerRef = any; + +export type EditorEvent = + | 'tokenizerUpdate' + | 'changeCursor' + | 'changeScrollTop' + | 'change' + | 'changeSelection'; + +export interface Position { + /** + * The line number, not zero-indexed. + * + * E.g., if given line number 1, this would refer to the first line visible. + */ + lineNumber: number; + + /** + * The column number, not zero-indexed. + * + * E.g., if given column number 1, this would refer to the first character of a column. + */ + column: number; +} + +export interface Range { + /** + * The start point of the range. + */ + start: Position; + + /** + * The end point of the range. + */ + end: Position; +} + +/** + * Enumeration of the different states the current position can be in. + * + * Current implementation uses low-level binary operations OR ('|') and AND ('&') to, respectively: + * + * - Create a combination of acceptable states. + * - Extract the states from the acceptable combination. + * + * E.g. + * ```ts + * const acceptableStates = LINE_MODE.REQUEST_START | LINE_MODE.IN_REQUEST; // binary '110' + * + * // Is MULTI_DOC_CUR_DOC_END ('1000') acceptable? + * Boolean(acceptableStates & LINE_MODE.MULTI_DOC_CUR_DOC_END) // false + * + * // Is REQUEST_START ('10') acceptable? + * Boolean(acceptableStates & LINE_MODE.REQUEST_START) // true + * ``` + * + * This implementation will probably be changed to something more accessible in future but is documented + * here for reference. + */ +export enum LINE_MODE { + REQUEST_START = 2, + IN_REQUEST = 4, + MULTI_DOC_CUR_DOC_END = 8, + REQUEST_END = 16, + BETWEEN_REQUESTS = 32, + UNKNOWN = 64, +} + +/** + * The CoreEditor is a component separate from the Editor implementation that provides Console + * app specific business logic. The CoreEditor is an interface to the lower-level editor implementation + * being used which is usually vendor code such as Ace or Monaco. + */ +export interface CoreEditor { + /** + * Get the current position of the cursor. + */ + getCurrentPosition(): Position; + + /** + * Get the contents of the editor. + */ + getValue(): string; + + /** + * Sets the contents of the editor. + * + * Returns a promise so that callers can wait for re-tokenizing to complete. + */ + setValue(value: string, forceRetokenize: boolean): Promise; + + /** + * Get the contents of the editor at a specific line. + */ + getLineValue(lineNumber: number): string; + + /** + * Insert a string value at the current cursor position. + */ + insert(value: string): void; + + /** + * Insert a string value at the indicated position. + */ + insert(pos: Position, value: string): void; + + /** + * Replace a range of text. + */ + replace(rangeToReplace: Range, value: string): void; + + /** + * Clear the selected range. + */ + clearSelection(): void; + + /** + * Returns the {@link Range} for currently selected text + */ + getSelectionRange(): Range; + + /** + * Move the cursor to the indicated position. + */ + moveCursorToPosition(pos: Position): void; + + /** + * Get the token at the indicated position. The token considered "at" the position is the + * one directly preceding the position. + * + * Returns null if there is no such token. + */ + getTokenAt(pos: Position): Token | null; + + /** + * Get an iterable token provider. + */ + getTokenProvider(): TokensProvider; + + /** + * Get the contents of the editor between two points. + */ + getValueInRange(range: Range): string; + + /** + * Get the lexer state at the end of a specific line. + */ + getLineState(lineNumber: number): string; + + /** + * Get line content between and including the start and end lines provided. + */ + getLines(startLine: number, endLine: number): string[]; + + /** + * Replace a range in the current buffer with the provided value. + */ + replaceRange(range: Range, value: string): void; + + /** + * Return the current line count in the buffer. + */ + getLineCount(): number; + + /** + * A legacy mechanism which gives consumers of this interface a chance to wait for + * latest tokenization to complete. + */ + waitForLatestTokens(): Promise; + + /** + * Mark a range in the current buffer + */ + addMarker(range: Range): MarkerRef; + + /** + * Mark a range in the current buffer + */ + removeMarker(ref: MarkerRef): void; + + /** + * Get a number that represents the current wrap limit on a line + */ + getWrapLimit(): number; + + /** + * Register a listener for predefined editor events + */ + on(event: EditorEvent, listener: () => void): void; + + /** + * Unregister a listener for predefined editor events + */ + off(event: EditorEvent, listener: () => void): void; + + /** + * Execute a predefined editor command. + */ + execCommand(cmd: string): void; + + /** + * Returns a boolean value indicating whether or not the completer UI is currently showing in + * the editor + */ + isCompleterActive(): boolean; + + /** + * Get the HTML container element for this editor instance + */ + getContainer(): HTMLDivElement; + + /** + * Because the core editor should not know about requests, but can know about ranges we still + * have this backdoor to update UI in response to request range changes, for example, as the user + * moves the cursor around + */ + legacyUpdateUI(opts: any): void; + + /** + * A method to for the editor to resize, useful when, for instance, window size changes. + */ + resize(): void; + + /** + * Expose a way to set styles on the editor + */ + setStyles(styles: { wrapLines: boolean; fontSize: string }): void; + + /** + * Register a keyboard shortcut and provide a function to be called. + */ + registerKeyboardShortcut(opts: { keys: any; fn: () => void; name: string }): void; +} diff --git a/src/legacy/core_plugins/console/np_ready/public/types/index.ts b/src/legacy/core_plugins/console/public/np_ready/types/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/index.ts rename to src/legacy/core_plugins/console/public/np_ready/types/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/token.ts b/src/legacy/core_plugins/console/public/np_ready/types/token.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/token.ts rename to src/legacy/core_plugins/console/public/np_ready/types/token.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/tokens_provider.ts b/src/legacy/core_plugins/console/public/np_ready/types/tokens_provider.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/tokens_provider.ts rename to src/legacy/core_plugins/console/public/np_ready/types/tokens_provider.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/index.scss b/src/legacy/core_plugins/console/public/quarantined/index.scss deleted file mode 100644 index ce40b2c6f1102a..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/index.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import 'src/legacy/ui/public/styles/styling_constants'; - -// Prefix all styles with "con" to avoid conflicts. -// Examples -// conChart -// conChart__legend -// conChart__legend--small -// conChart__legend-isLoading - -@import './app'; -@import './src/directives/index'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/__tests__/utils.js b/src/legacy/core_plugins/console/public/quarantined/src/__tests__/utils.js deleted file mode 100644 index fa88e42fa557f4..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/__tests__/utils.js +++ /dev/null @@ -1,36 +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 utils from '../utils'; - -describe('console utils', () => { - describe('collapseLiteralStrings', () => { - it('will collapse multiline strings', () => { - const multiline = '{ "foo": """bar\nbaz""" }'; - expect(utils.collapseLiteralStrings(multiline)).to.be('{ "foo": "bar\\nbaz" }'); - }); - - it('will collapse multiline strings with CRLF endings', () => { - const multiline = '{ "foo": """bar\r\nbaz""" }'; - expect(utils.collapseLiteralStrings(multiline)).to.be('{ "foo": "bar\\r\\nbaz" }'); - }); - }); -}); diff --git a/src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss b/src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss deleted file mode 100644 index 56bd7ed20bda47..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './help'; -@import './history'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/input.ts b/src/legacy/core_plugins/console/public/quarantined/src/input.ts deleted file mode 100644 index eb93f8e165cb57..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/input.ts +++ /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. - */ - -// @ts-ignore -import Autocomplete from './autocomplete'; - -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) { - input = new SenseEditor($el); - - // Autocomplete should not use any Ace functionality directly - // so we build a shim here. - const editorShim = { - coreEditor: new LegacyEditor(input), - parser: input.parser, - execCommand: (cmd: string) => input.execCommand(cmd), - 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), - }; - - input.autocomplete = new (Autocomplete as any)(editorShim); - input.setOptions({ - enableBasicAutocompletion: true, - }); - input.$blockScrolling = Infinity; - input.$actions = $actionsEl; - - /** - * Init the editor - */ - input.focus(); - input.highlightCurrentRequestsAndUpdateActionBar(); - - return input; -} - -// eslint-disable-next-line -export default function getInput() { - return input; -} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/output.js b/src/legacy/core_plugins/console/public/quarantined/src/output.js deleted file mode 100644 index 42555f17f19c1d..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/output.js +++ /dev/null @@ -1,86 +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 ace = require('brace'); -const OutputMode = require('./sense_editor/mode/output'); -const smartResize = require('./smart_resize'); - -let output; -export function initializeOutput($el) { - output = ace.acequire('ace/ace').edit($el[0]); - - const outputMode = new OutputMode.Mode(); - - output.$blockScrolling = Infinity; - output.resize = smartResize(output); - output.update = (val, mode, cb) => { - if (typeof mode === 'function') { - cb = mode; - mode = void 0; - } - - const session = output.getSession(); - - session.setMode(val ? mode || outputMode : 'ace/mode/text'); - session.setValue(val); - if (typeof cb === 'function') { - setTimeout(cb); - } - }; - - output.append = (val, foldPrevious, cb) => { - if (typeof foldPrevious === 'function') { - cb = foldPrevious; - foldPrevious = true; - } - if (_.isUndefined(foldPrevious)) { - foldPrevious = true; - } - const session = output.getSession(); - const lastLine = session.getLength(); - if (foldPrevious) { - output.moveCursorTo(Math.max(0, lastLine - 1), 0); - } - session.insert({ row: lastLine, column: 0 }, '\n' + val); - output.moveCursorTo(lastLine + 1, 0); - if (typeof cb === 'function') { - setTimeout(cb); - } - }; - - output.$el = $el; - - // eslint-disable-next-line - (function setupSession(session) { - session.setMode('ace/mode/text'); - session.setFoldStyle('markbeginend'); - session.setTabSize(2); - session.setUseWrapMode(true); - })(output.getSession()); - - output.setShowPrintMargin(false); - output.setReadOnly(true); - - return output; -} - -export default function getOutput() { - return output; -} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js b/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js deleted file mode 100644 index 9c34860df35ff0..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js +++ /dev/null @@ -1,676 +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. - */ - -const _ = require('lodash'); -const ace = require('brace'); -const $ = require('jquery'); -const curl = require('../curl'); -const RowParser = require('./row_parser'); -const InputMode = require('./mode/input'); -const utils = require('../utils'); -const es = require('../es'); -const chrome = require('ui/chrome'); - -const smartResize = require('../smart_resize'); - -function createInstance($el) { - const aceEditor = ace.edit($el[0]); - - // we must create a custom class for each instance, so that the prototype - // can be the unique aceEditor it extends - const CustomSenseEditor = function () { - }; - CustomSenseEditor.prototype = {}; - - function bindProp(key) { - Object.defineProperty(CustomSenseEditor.prototype, key, { - get: function () { - return aceEditor[key]; - }, - set: function (val) { - aceEditor[key] = val; - } - }); - } - - // iterate all of the accessible properties/method, on the prototype and beyond - // eslint-disable-next-line guard-for-in - for (const key in aceEditor) { - switch (typeof aceEditor[key]) { - case 'function': - CustomSenseEditor.prototype[key] = _.bindKey(aceEditor, key); - break; - default: - bindProp(key); - break; - } - } - - const editor = new CustomSenseEditor(); - editor.__ace = aceEditor; - return editor; -} - -export default function SenseEditor($el) { - const editor = createInstance($el); - let CURRENT_REQ_RANGE = null; - - editor.$el = $el; - // place holder for an action bar, needs to be set externally. - editor.$actions = null; - - // mixin the RowParser - editor.parser = new RowParser(editor); - editor.resize = smartResize(editor); - - // dirty check for tokenizer state, uses a lot less cycles - // than listening for tokenizerUpdate - const onceDoneTokenizing = function (callback, cancelAlreadyScheduledCalls) { - const session = editor.getSession(); - let timer = false; - const checkInterval = 25; - - return function () { - const self = this; - const args = [].slice.call(arguments, 0); - - if (cancelAlreadyScheduledCalls) { - timer = clearTimeout(timer); - } - - setTimeout(function check() { - // If the bgTokenizer doesn't exist, we can assume that the underlying editor has been - // torn down, e.g. by closing the History tab, and we don't need to do anything further. - if (session.bgTokenizer) { - // Wait until the bgTokenizer is done running before executing the callback. - if (session.bgTokenizer.running) { - timer = setTimeout(check, checkInterval); - } else { - callback.apply(self, args); - } - } - }); - }; - }; - - editor.setShowPrintMargin(false); - (function (session) { - session.setMode(new InputMode.Mode()); - session.setFoldStyle('markbeginend'); - session.setTabSize(2); - session.setUseWrapMode(true); - }(editor.getSession())); - - editor.prevRequestStart = function (rowOrPos) { - rowOrPos = _.isUndefined(rowOrPos) || rowOrPos === null ? editor.getCursorPosition() : rowOrPos; - - let curRow = _.isObject(rowOrPos) ? rowOrPos.row : rowOrPos; - while (curRow > 0 && !editor.parser.isStartRequestRow(curRow, editor)) curRow--; - - return { - row: curRow, - column: 0 - }; - }; - - editor.nextRequestStart = function (rowOrPos) { - rowOrPos = _.isUndefined(rowOrPos) || rowOrPos === null ? editor.getCursorPosition() : rowOrPos; - const session = editor.getSession(); - let curRow = _.isObject(rowOrPos) ? rowOrPos.row : rowOrPos; - const maxLines = session.getLength(); - for (; curRow < maxLines - 1; curRow++) { - if (editor.parser.isStartRequestRow(curRow, editor)) { - break; - } - } - return { - row: curRow, - column: 0 - }; - }; - - editor.autoIndent = onceDoneTokenizing(function () { - editor.getRequestRange(function (reqRange) { - if (!reqRange) { - return; - } - editor.getRequest(function (parsedReq) { - if (parsedReq.data && parsedReq.data.length > 0) { - let indent = parsedReq.data.length === 1; // unindent multi docs by default - let formattedData = utils.reformatData(parsedReq.data, indent); - if (!formattedData.changed) { - // toggle. - indent = !indent; - formattedData = utils.reformatData(parsedReq.data, indent); - } - parsedReq.data = formattedData.data; - - editor.replaceRequestRange(parsedReq, reqRange); - } - }); - }); - }, true); - - editor.update = function (data, callback) { - callback = typeof callback === 'function' ? callback : null; - const session = editor.getSession(); - - session.setValue(data); - if (callback) { - // force update of tokens, but not on this thread to allow for ace rendering. - setTimeout(function () { - let i; - for (i = 0; i < session.getLength(); i++) { - session.getTokens(i); - } - callback(); - }); - } - - }; - - editor.replaceRequestRange = function (newRequest, requestRange) { - const text = utils.textFromRequest(newRequest); - if (requestRange) { - const pos = editor.getCursorPosition(); - editor.getSession().replace(requestRange, text); - const maxRow = Math.max(requestRange.start.row + text.split('\n').length - 1, 0); - pos.row = Math.min(pos.row, maxRow); - editor.moveCursorToPosition(pos); - // ACE UPGRADE - check if needed - at the moment the above may trigger a selection. - editor.clearSelection(); - } - else { - // just insert where we are - editor.insert(text); - } - }; - - editor.iterForCurrentLoc = function () { - const pos = editor.getCursorPosition(); - return editor.iterForPosition(pos.row, pos.column, editor); - }; - - editor.iterForPosition = function (row, column) { - return new (ace.acequire('ace/token_iterator').TokenIterator)(editor.getSession(), row, column); - }; - - editor.getRequestRange = onceDoneTokenizing(function (row, cb) { - if (_.isUndefined(cb)) { - cb = row; - row = null; - } - if (typeof cb !== 'function') { - return; - } - - if (editor.parser.isInBetweenRequestsRow(row)) { - cb(null); - return; - } - - const reqStart = editor.prevRequestStart(row, editor); - const reqEnd = editor.nextRequestEnd(reqStart, editor); - cb(new (ace.acequire('ace/range').Range)( - reqStart.row, reqStart.column, - reqEnd.row, reqEnd.column - )); - }); - - editor.getEngulfingRequestsRange = onceDoneTokenizing(function (range, cb) { - if (_.isUndefined(cb)) { - cb = range; - range = null; - } - - range = range || editor.getSelectionRange(); - - const session = editor.getSession(); - let startRow = range.start.row; - let endRow = range.end.row; - const maxLine = Math.max(0, session.getLength() - 1); - - // move start row to the previous request start if in body, o.w. forward - if (editor.parser.isInBetweenRequestsRow(startRow)) { - //for (; startRow <= endRow; startRow++) { - // if (editor.parser.isStartRequestRow(startRow)) { - // break; - // } - //} - } - else { - for (; startRow >= 0; startRow--) { - if (editor.parser.isStartRequestRow(startRow)) { - break; - } - } - } - - if (startRow < 0 || startRow > endRow) { - cb(null); - return; - } - // move end row to the previous request end if between requests, o.w. walk forward - if (editor.parser.isInBetweenRequestsRow(endRow)) { - for (; endRow >= startRow; endRow--) { - if (editor.parser.isEndRequestRow(endRow)) { - break; - } - } - } - else { - - for (; endRow <= maxLine; endRow++) { - if (editor.parser.isEndRequestRow(endRow)) { - break; - } - } - - } - - if (endRow < startRow || endRow > maxLine) { - cb(null); - return; - } - - const endColumn = (session.getLine(endRow) || '').replace(/\s+$/, '').length; - cb(new (ace.acequire('ace/range').Range)(startRow, 0, endRow, endColumn)); - }); - - - editor.getRequestInRange = onceDoneTokenizing(function (range, cb) { - if (!range) { - return; - } - const request = { - method: '', - data: [], - url: null, - range: range - }; - - const pos = range.start; - const tokenIter = editor.iterForPosition(pos.row, pos.column, editor); - let t = tokenIter.getCurrentToken(); - if (editor.parser.isEmptyToken(t)) { - // if the row starts with some spaces, skip them. - t = editor.parser.nextNonEmptyToken(tokenIter); - } - request.method = t.value; - t = editor.parser.nextNonEmptyToken(tokenIter); - if (!t || t.type === 'method') { - return null; - } - request.url = ''; - while (t && t.type && t.type.indexOf('url') === 0) { - request.url += t.value; - t = tokenIter.stepForward(); - } - if (editor.parser.isEmptyToken(t)) { - // if the url row ends with some spaces, skip them. - t = editor.parser.nextNonEmptyToken(tokenIter); - } - - let bodyStartRow = (t ? 0 : 1) + tokenIter.getCurrentTokenRow(); // artificially increase end of docs. - let dataEndPos; - while (bodyStartRow < range.end.row || ( - bodyStartRow === range.end.row && 0 < range.end.column - )) { - dataEndPos = editor.nextDataDocEnd({ - row: bodyStartRow, - column: 0 - }); - const bodyRange = new (ace.acequire('ace/range').Range)( - bodyStartRow, 0, - dataEndPos.row, dataEndPos.column - ); - const data = editor.getSession().getTextRange(bodyRange); - request.data.push(data.trim()); - bodyStartRow = dataEndPos.row + 1; - } - - cb(request); - }); - - editor.getRequestsInRange = function (range, includeNonRequestBlocks, cb) { - if (_.isUndefined(includeNonRequestBlocks)) { - includeNonRequestBlocks = false; - cb = range; - range = null; - } else if (_.isUndefined(cb)) { - cb = includeNonRequestBlocks; - includeNonRequestBlocks = false; - } - - function explicitRangeToRequests(requestsRange, tempCb) { - if (!requestsRange) { - tempCb([]); - return; - } - - const startRow = requestsRange.start.row; - const endRow = requestsRange.end.row; - - // move to the next request start (during the second iterations this may not be exactly on a request - let currentRow = startRow; - for (; currentRow <= endRow; currentRow++) { - if (editor.parser.isStartRequestRow(currentRow)) { - break; - } - } - - let nonRequestPrefixBlock = null; - if (includeNonRequestBlocks && currentRow !== startRow) { - nonRequestPrefixBlock = editor.getSession().getLines(startRow, currentRow - 1).join('\n'); - } - - if (currentRow > endRow) { - tempCb(nonRequestPrefixBlock ? [nonRequestPrefixBlock] : []); - return; - } - - editor.getRequest(currentRow, function (request) { - if (!request) { - return; - } - explicitRangeToRequests({ - start: { - row: request.range.end.row + 1 - }, - end: { - row: requestsRange.end.row - } - }, - function (restOfRequests) { - restOfRequests.unshift(request); - if (nonRequestPrefixBlock !== null) { - restOfRequests.unshift(nonRequestPrefixBlock); - } - tempCb(restOfRequests); - } - ); - }); - } - - editor.getEngulfingRequestsRange(range, function (requestRange) { - explicitRangeToRequests(requestRange, cb); - }); - }; - - editor.getRequest = onceDoneTokenizing(function (row, cb) { - if (_.isUndefined(cb)) { - cb = row; - row = null; - } - if (typeof cb !== 'function') { - return; - } - if (editor.parser.isInBetweenRequestsRow(row)) { - cb(null); - return; - } - editor.getRequestRange(row, function (range) { - editor.getRequestInRange(range, cb); - }); - }); - - editor.moveToPreviousRequestEdge = onceDoneTokenizing(function () { - const pos = editor.getCursorPosition(); - for (pos.row--; pos.row > 0 && !editor.parser.isRequestEdge(pos.row); pos.row--) { - // loop for side effects - } - editor.moveCursorTo(pos.row, 0); - }); - - editor.moveToNextRequestEdge = onceDoneTokenizing(function (moveOnlyIfNotOnEdge) { - const pos = editor.getCursorPosition(); - const maxRow = editor.getSession().getLength(); - if (!moveOnlyIfNotOnEdge) { - pos.row++; - } - for (; pos.row < maxRow && !editor.parser.isRequestEdge(pos.row); pos.row++) { - // loop for side effects - } - editor.moveCursorTo(pos.row, 0); - }); - - editor.nextRequestEnd = function (pos) { - pos = pos || editor.getCursorPosition(); - const session = editor.getSession(); - let curRow = pos.row; - const maxLines = session.getLength(); - for (; curRow < maxLines - 1; curRow++) { - const curRowMode = editor.parser.getRowParseMode(curRow, editor); - if ((curRowMode & editor.parser.MODE.REQUEST_END) > 0) { - break; - } - if (curRow !== pos.row && (curRowMode & editor.parser.MODE.REQUEST_START) > 0) { - break; - } - } - - const column = (session.getLine(curRow) || '').replace(/\s+$/, '').length; - - return { - row: curRow, - column: column - }; - }; - - editor.nextDataDocEnd = function (pos) { - pos = pos || editor.getCursorPosition(); - const session = editor.getSession(); - let curRow = pos.row; - const maxLines = session.getLength(); - for (; curRow < maxLines - 1; curRow++) { - const curRowMode = editor.parser.getRowParseMode(curRow, editor); - if ((curRowMode & RowParser.REQUEST_END) > 0) { - break; - } - if ((curRowMode & editor.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) { - break; - } - if (curRow !== pos.row && (curRowMode & editor.parser.MODE.REQUEST_START) > 0) { - break; - } - } - - const column = (session.getLine(curRow) || '').length; - - return { - row: curRow, - column: column - }; - }; - - // overwrite the actual aceEditor's onPaste method - const origOnPaste = editor.__ace.onPaste; - editor.__ace.onPaste = function (text) { - if (text && curl.detectCURL(text)) { - editor.handleCURLPaste(text); - return; - } - origOnPaste.call(this, text); - }; - - editor.handleCURLPaste = function (text) { - const curlInput = curl.parseCURL(text); - - editor.insert(curlInput); - }; - - editor.highlightCurrentRequestsAndUpdateActionBar = onceDoneTokenizing(function () { - const session = editor.getSession(); - editor.getEngulfingRequestsRange(function (newCurrentReqRange) { - if (newCurrentReqRange === null && CURRENT_REQ_RANGE === null) { - return; - } - if (newCurrentReqRange !== null && CURRENT_REQ_RANGE !== null && - newCurrentReqRange.start.row === CURRENT_REQ_RANGE.start.row && - newCurrentReqRange.end.row === CURRENT_REQ_RANGE.end.row - ) { - // same request, now see if we are on the first line and update the action bar - const cursorRow = editor.getCursorPosition().row; - if (cursorRow === CURRENT_REQ_RANGE.start.row) { - editor.updateActionsBar(); - } - return; // nothing to do.. - } - - if (CURRENT_REQ_RANGE) { - session.removeMarker(CURRENT_REQ_RANGE.marker_id); - } - - CURRENT_REQ_RANGE = newCurrentReqRange; - if (CURRENT_REQ_RANGE) { - CURRENT_REQ_RANGE.marker_id = session.addMarker(CURRENT_REQ_RANGE, 'ace_snippet-marker', 'fullLine'); - } - editor.updateActionsBar(); - }); - }, true); - - editor.getRequestsAsCURL = function (range, cb) { - if (_.isUndefined(cb)) { - cb = range; - range = null; - } - - if (_.isUndefined(cb)) { - cb = $.noop; - } - - editor.getRequestsInRange(range, true, function (requests) { - - const result = _.map(requests, function requestToCurl(req) { - - if (typeof req === 'string') { - // no request block - return req; - } - - const esPath = req.url; - const esMethod = req.method; - const esData = req.data; - - // this is the first url defined in elasticsearch.hosts - const elasticsearchBaseUrl = chrome.getInjected('elasticsearchUrl'); - const url = es.constructESUrl(elasticsearchBaseUrl, esPath); - - let ret = 'curl -X' + esMethod + ' "' + url + '"'; - if (esData && esData.length) { - ret += ' -H \'Content-Type: application/json\' -d\'\n'; - const dataAsString = utils.collapseLiteralStrings(esData.join('\n')); - // since Sense doesn't allow single quote json string any single qoute is within a string. - ret += dataAsString.replace(/'/g, '\\"'); - if (esData.length > 1) { - ret += '\n'; - } // end with a new line - ret += '\''; - } - return ret; - }); - - cb(result.join('\n')); - }); - }; - - editor.getSession().on('tokenizerUpdate', function () { - editor.highlightCurrentRequestsAndUpdateActionBar(); - }); - - editor.getSession().selection.on('changeCursor', function () { - editor.highlightCurrentRequestsAndUpdateActionBar(); - }); - - editor.updateActionsBar = (function () { - const set = function (top) { - if (top === null) { - editor.$actions.css('visibility', 'hidden'); - } - else { - editor.$actions.css({ - top: top, - visibility: 'visible' - }); - } - }; - - const hide = function () { - set(); - }; - - return function () { - if (!editor.$actions) { - return; - } - if (CURRENT_REQ_RANGE) { - // elements are positioned relative to the editor's container - // pageY is relative to page, so subtract the offset - // from pageY to get the new top value - const offsetFromPage = editor.$el.offset().top; - const startRow = CURRENT_REQ_RANGE.start.row; - const startColumn = CURRENT_REQ_RANGE.start.column; - const session = editor.session; - const firstLine = session.getLine(startRow); - const maxLineLength = session.getWrapLimit() - 5; - const isWrapping = firstLine.length > maxLineLength; - const getScreenCoords = (row) => editor.renderer.textToScreenCoordinates(row, startColumn).pageY - offsetFromPage; - const topOfReq = getScreenCoords(startRow); - - if (topOfReq >= 0) { - let offset = 0; - if (isWrapping) { - // Try get the line height of the text area in pixels. - const textArea = editor.$el.find('textArea'); - const hasRoomOnNextLine = session.getLine(startRow + 1).length < maxLineLength; - if (textArea && hasRoomOnNextLine) { - // Line height + the number of wraps we have on a line. - offset += (session.getRowLength(startRow) * textArea.height()); - } else { - if (startRow > 0) { - set(getScreenCoords(startRow - 1, startColumn)); - return; - } - set(getScreenCoords(startRow + 1, startColumn)); - return; - } - } - set(topOfReq + offset); - return; - } - - const bottomOfReq = editor.renderer.textToScreenCoordinates( - CURRENT_REQ_RANGE.end.row, - CURRENT_REQ_RANGE.end.column - ).pageY - offsetFromPage; - - if (bottomOfReq >= 0) { - set(0); - return; - } - } - - hide(); - }; - }()); - - editor.getSession().on('changeScrollTop', editor.updateActionsBar); - - return editor; -} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js b/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js deleted file mode 100644 index c1bba107de784c..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js +++ /dev/null @@ -1,147 +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. - */ - -const MODE = { - REQUEST_START: 2, - IN_REQUEST: 4, - MULTI_DOC_CUR_DOC_END: 8, - REQUEST_END: 16, - BETWEEN_REQUESTS: 32 -}; - -/** - * The RowParser is still using Ace editor directly for now. - * - * This will be cleaned up when we implement the editor interface everywhere - * in the next pass. - */ -function RowParser(editor) { - const defaultEditor = editor; - - this.getRowParseMode = function (row) { - if (row === null || typeof row === 'undefined') { - row = editor.getCursorPosition().row; - } - - const session = editor.getSession(); - if (row >= session.getLength() || row < 0) { - return MODE.BETWEEN_REQUESTS; - } - const mode = session.getState(row); - if (!mode) { - return MODE.BETWEEN_REQUESTS; - } // shouldn't really happen - - if (mode !== 'start') { - return MODE.IN_REQUEST; - } - let line = (session.getLine(row) || '').trim(); - if (!line || line[0] === '#') { - return MODE.BETWEEN_REQUESTS; - } // empty line or a comment waiting for a new req to start - - if (line.indexOf('}', line.length - 1) >= 0) { - // check for a multi doc request (must start a new json doc immediately after this one end. - row++; - if (row < session.getLength()) { - line = (session.getLine(row) || '').trim(); - if (line.indexOf('{') === 0) { // next line is another doc in a multi doc - return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST; - } - - } - return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request - } - - // check for single line requests - row++; - if (row >= session.getLength()) { - return MODE.REQUEST_START | MODE.REQUEST_END; - } - line = (session.getLine(row) || '').trim(); - if (line.indexOf('{') !== 0) { // next line is another request - return MODE.REQUEST_START | MODE.REQUEST_END; - } - - return MODE.REQUEST_START; - }; - - this.rowPredicate = function (row, editor, value) { - const mode = this.getRowParseMode(row, editor); - return (mode & value) > 0; - }; - - this.isEndRequestRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.REQUEST_END); - }; - - this.isRequestEdge = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START); - }; - - this.isStartRequestRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.REQUEST_START); - }; - - this.isInBetweenRequestsRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS); - }; - - this.isInRequestsRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.IN_REQUEST); - }; - - this.isMultiDocDocEndRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END); - }; - - this.isEmptyToken = function (tokenOrTokenIter) { - const token = tokenOrTokenIter && tokenOrTokenIter.getCurrentToken ? tokenOrTokenIter.getCurrentToken() : tokenOrTokenIter; - return !token || token.type === 'whitespace'; - }; - - this.isUrlOrMethodToken = function (tokenOrTokenIter) { - const t = tokenOrTokenIter.getCurrentToken ? tokenOrTokenIter.getCurrentToken() : tokenOrTokenIter; - return t && t.type && (t.type === 'method' || t.type.indexOf('url') === 0); - }; - - - this.nextNonEmptyToken = function (tokenIter) { - let t = tokenIter.stepForward(); - while (t && this.isEmptyToken(t)) t = tokenIter.stepForward(); - return t; - }; - - this.prevNonEmptyToken = function (tokenIter) { - let t = tokenIter.stepBackward(); - // empty rows return null token. - while ((t || tokenIter.getCurrentPosition().lineNumber > 1) && this.isEmptyToken(t)) t = tokenIter.stepBackward(); - return t; - }; -} - -RowParser.prototype.MODE = MODE; - -export default RowParser; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/smart_resize.js b/src/legacy/core_plugins/console/public/quarantined/src/smart_resize.js deleted file mode 100644 index 8ac5eda345c238..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/smart_resize.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 { get, throttle } from 'lodash'; - -export default function (editor) { - const resize = editor.resize; - - const throttledResize = throttle(() => { - resize.call(editor); - - // Keep current top line in view when resizing to avoid losing user context - const userRow = get(throttledResize, 'topRow', 0); - if (userRow !== 0) { - editor.renderer.scrollToLine(userRow, false, false, () => {}); - } - }, 35); - return throttledResize; -} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/utils.js b/src/legacy/core_plugins/console/public/quarantined/src/utils.js deleted file mode 100644 index 5b6bd1646c3006..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/utils.js +++ /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. - */ - -import _ from 'lodash'; - -const utils = {}; - -utils.textFromRequest = function (request) { - let data = request.data; - if (typeof data !== 'string') { - data = data.join('\n'); - } - return request.method + ' ' + request.url + '\n' + data; -}; - -utils.jsonToString = function (data, indent) { - return JSON.stringify(data, null, indent ? 2 : 0); -}; - -utils.reformatData = function (data, indent) { - let changed = false; - const formattedData = []; - for (let i = 0; i < data.length; i++) { - const curDoc = data[i]; - try { - let newDoc = utils.jsonToString(JSON.parse(utils.collapseLiteralStrings(curDoc)), indent ? 2 : 0); - if (indent) { - newDoc = utils.expandLiteralStrings(newDoc); - } - changed = changed || newDoc !== curDoc; - formattedData.push(newDoc); - } - catch (e) { - console.log(e); - formattedData.push(curDoc); - } - } - - return { - changed: changed, - data: formattedData - }; -}; - -utils.collapseLiteralStrings = function (data) { - const splitData = data.split(`"""`); - for (let idx = 1; idx < splitData.length - 1; idx += 2) { - splitData[idx] = JSON.stringify(splitData[idx]); - } - return splitData.join(''); -}; - -/* - The following regex describes global match on: - 1. one colon followed by any number of space characters - 2. one double quote (not escaped, special case for JSON in JSON). - 3. greedily match any non double quote and non newline char OR any escaped double quote char (non-capturing). - 4. handle a special case where an escaped slash may be the last character - 5. one double quote - - For instance: `: "some characters \" here"` - Will match and be expanded to: `"""some characters " here"""` - - */ - -const LITERAL_STRING_CANDIDATES = /((:[\s\r\n]*)([^\\])"(\\"|[^"\n])*\\?")/g; - -utils.expandLiteralStrings = function (data) { - return data.replace(LITERAL_STRING_CANDIDATES, function (match, string) { - // Expand to triple quotes if there are _any_ slashes - if (string.match(/\\./)) { - const firstDoubleQuoteIdx = string.indexOf('"'); - const colonAndAnySpacing = string.slice(0, firstDoubleQuoteIdx); - const rawStringifiedValue = string.slice(firstDoubleQuoteIdx, string.length); - const jsonValue = JSON.parse(rawStringifiedValue) - .replace('^\s*\n', '') - .replace('\n\s*^', ''); - return `${colonAndAnySpacing}"""${jsonValue}"""`; - } else { - return string; - } - }); -}; - -utils.extractDeprecationMessages = function (warnings) { - // pattern for valid warning header - const re = /\d{3} [0-9a-zA-Z!#$%&'*+-.^_`|~]+ \"((?:\t| |!|[\x23-\x5b]|[\x5d-\x7e]|[\x80-\xff]|\\\\|\\")*)\"(?: \"[^"]*\")?/; - // split on any comma that is followed by an even number of quotes - return _.map(utils.splitOnUnquotedCommaSpace(warnings), function (warning) { - const match = re.exec(warning); - // extract the actual warning if there was a match - return '#! Deprecation: ' + (match !== null ? utils.unescape(match[1]) : warning); - }); -}; - -utils.unescape = function (s) { - return s.replace(/\\\\/g, '\\').replace(/\\"/g, '"'); -}; - -utils.splitOnUnquotedCommaSpace = function (s) { - let quoted = false; - const arr = []; - let buffer = ''; - let i = 0; - while (i < s.length) { - let token = s.charAt(i++); - if (token === '\\' && i < s.length) { - token += s.charAt(i++); - } else if (token === ',' && i < s.length && s.charAt(i) === ' ') { - token += s.charAt(i++); - } - if (token === '"') { - quoted = !quoted; - } else if (!quoted && token === ', ') { - arr.push(buffer); - buffer = ''; - continue; - } - buffer += token; - } - arr.push(buffer); - return arr; -}; - -export default utils; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/editor.test.js b/src/legacy/core_plugins/console/public/quarantined/tests/src/editor.test.js deleted file mode 100644 index f828106e2bd419..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/editor.test.js +++ /dev/null @@ -1,485 +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 './setup_mocks'; -import $ from 'jquery'; -import _ from 'lodash'; -import ace from 'brace'; -import 'brace/mode/json'; - -import { initializeEditor } from '../../src/input'; -const editorInput1 = require('./editor_input1.txt'); -const utils = require('../../src/utils'); - -const aceRange = ace.acequire('ace/range'); - -describe('Editor', () => { - let input; - - beforeEach(function () { - // Set up our document body - document.body.innerHTML = - `
-
-
-
-
`; - - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions'), - ); - input.$el.show(); - input.autocomplete._test.removeChangeListener(); - }); - afterEach(function () { - input.$el.hide(); - input.autocomplete._test.addChangeListener(); - }); - - let testCount = 0; - - const callWithEditorMethod = (editorMethod, fn) => (done) => { - try { - input[editorMethod](results => fn(results, done)); - } catch { - done(); - } - }; - - function utilsTest(name, prefix, data, testToRun) { - const id = testCount++; - if (typeof data === 'function') { - testToRun = data; - data = null; - } - if (data && typeof data !== 'string') { - data = JSON.stringify(data, null, 3); - } - if (data) { - if (prefix) { - data = prefix + '\n' + data; - } - } else { - data = prefix; - } - - test('Utils test ' + id + ' : ' + name, function (done) { - input.update(data, function () { - testToRun(done); - }); - }); - } - - function compareRequest(requests, expected) { - if (!Array.isArray(requests)) { - requests = [requests]; - expected = [expected]; - } - - _.each(requests, function (r) { - delete r.range; - }); - expect(requests).toEqual(expected); - } - - const simpleRequest = { - prefix: 'POST _search', - data: ['{', ' "query": { "match_all": {} }', '}'].join('\n'), - }; - - const singleLineRequest = { - prefix: 'POST _search', - data: '{ "query": { "match_all": {} } }', - }; - - const getRequestNoData = { - prefix: 'GET _stats', - }; - - const multiDocRequest = { - prefix: 'POST _bulk', - data_as_array: [ - '{ "index": { "_index": "index", "_type":"type" } }', - '{ "field": 1 }', - ], - }; - multiDocRequest.data = multiDocRequest.data_as_array.join('\n'); - - utilsTest( - 'simple request range', - simpleRequest.prefix, - simpleRequest.data, - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 3, 1); - compareRequest(range, expected); - done(); - }) - ); - - utilsTest( - 'simple request data', - simpleRequest.prefix, - simpleRequest.data, - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'POST', - url: '_search', - data: [simpleRequest.data], - }; - compareRequest(request, expected); - done(); - }) - ); - - utilsTest( - 'simple request range, prefixed with spaces', - ' ' + simpleRequest.prefix, - simpleRequest.data, - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 3, 1); - expect(range).toEqual(expected); - done(); - }) - ); - - utilsTest( - 'simple request data, prefixed with spaces', - ' ' + simpleRequest.prefix, - simpleRequest.data, - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'POST', - url: '_search', - data: [simpleRequest.data], - }; - - compareRequest(request, expected); - done(); - }) - ); - - - utilsTest( - 'simple request range, suffixed with spaces', - simpleRequest.prefix + ' ', - simpleRequest.data + ' ', - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 3, 1); - compareRequest(range, expected); - done(); - }) - ); - - utilsTest( - 'simple request data, suffixed with spaces', - simpleRequest.prefix + ' ', - simpleRequest.data + ' ', - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'POST', - url: '_search', - data: [simpleRequest.data], - }; - - compareRequest(request, expected); - done(); - }) - ); - - - utilsTest( - 'single line request range', - singleLineRequest.prefix, - singleLineRequest.data, - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 1, 32); - compareRequest(range, expected); - done(); - }) - ); - - utilsTest( - 'full url: single line request data', - 'POST https://somehost/_search', - singleLineRequest.data, - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'POST', - url: 'https://somehost/_search', - data: [singleLineRequest.data], - }; - compareRequest(request, expected); - done(); - }) - ); - - utilsTest( - 'request with no data followed by a new line', - getRequestNoData.prefix, - '\n', - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 0, 10); - compareRequest(range, expected); - done(); - }) - ); - - utilsTest( - 'request with no data followed by a new line (data)', - getRequestNoData.prefix, - '\n', - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'GET', - url: '_stats', - data: [], - }; - compareRequest(request, expected); - done(); - }) - ); - - utilsTest( - 'request with no data', - getRequestNoData.prefix, - getRequestNoData.data, - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 0, 10); - expect(range).toEqual(expected); - done(); - }) - ); - - utilsTest( - 'request with no data (data)', - getRequestNoData.prefix, - getRequestNoData.data, - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'GET', - url: '_stats', - data: [], - }; - compareRequest(request, expected); - done(); - }) - ); - - utilsTest( - 'multi doc request range', - multiDocRequest.prefix, - multiDocRequest.data, - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 2, 14); - expect(range).toEqual(expected); - done(); - }) - ); - - utilsTest( - 'multi doc request data', - multiDocRequest.prefix, - multiDocRequest.data, - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'POST', - url: '_bulk', - data: multiDocRequest.data_as_array, - }; - compareRequest(request, expected); - done(); - }) - ); - - const scriptRequest = { - prefix: 'POST _search', - data: [ - '{', - ' "query": { "script": """', - ' some script ', - ' """}', - '}', - ].join('\n'), - }; - - utilsTest( - 'script request range', - scriptRequest.prefix, - scriptRequest.data, - callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 5, 1); - compareRequest(range, expected); - done(); - }) - ); - - - utilsTest( - 'simple request data', - simpleRequest.prefix, - simpleRequest.data, - callWithEditorMethod('getRequest', (request, done) => { - const expected = { - method: 'POST', - url: '_search', - data: [utils.collapseLiteralStrings(simpleRequest.data)], - }; - - compareRequest(request, expected); - done(); - }) - ); - - function multiReqTest(name, editorInput, range, expected) { - utilsTest('multi request select - ' + name, editorInput, function (done) { - input.getRequestsInRange(range, function (requests) { - // convert to format returned by request. - _.each(expected, function (req) { - req.data = - req.data == null ? [] : [JSON.stringify(req.data, null, 2)]; - }); - - compareRequest(requests, expected); - done(); - }); - }); - } - - multiReqTest( - 'mid body to mid body', - editorInput1, - { start: { row: 12 }, end: { row: 17 } }, - [ - { - method: 'PUT', - url: 'index_1/type1/1', - data: { - f: 1, - }, - }, - { - method: 'PUT', - url: 'index_1/type1/2', - data: { - f: 2, - }, - }, - ] - ); - - multiReqTest( - 'single request start to end', - editorInput1, - { start: { row: 10 }, end: { row: 13 } }, - [ - { - method: 'PUT', - url: 'index_1/type1/1', - data: { - f: 1, - }, - }, - ] - ); - - multiReqTest( - 'start to end, with comment', - editorInput1, - { start: { row: 6 }, end: { row: 13 } }, - [ - { - method: 'GET', - url: '_stats?level=shards', - data: null, - }, - { - method: 'PUT', - url: 'index_1/type1/1', - data: { - f: 1, - }, - }, - ] - ); - - multiReqTest( - 'before start to after end, with comments', - editorInput1, - { start: { row: 4 }, end: { row: 14 } }, - [ - { - method: 'GET', - url: '_stats?level=shards', - data: null, - }, - { - method: 'PUT', - url: 'index_1/type1/1', - data: { - f: 1, - }, - }, - ] - ); - - multiReqTest( - 'between requests', - editorInput1, - { start: { row: 21 }, end: { row: 22 } }, - [] - ); - - multiReqTest( - 'between requests - with comment', - editorInput1, - { start: { row: 20 }, end: { row: 22 } }, - [] - ); - - multiReqTest( - 'between requests - before comment', - editorInput1, - { start: { row: 19 }, end: { row: 22 } }, - [] - ); - - function multiReqCopyAsCurlTest(name, editorInput, range, expected) { - utilsTest('multi request copy as curl - ' + name, editorInput, function (done) { - input.getRequestsAsCURL(range, function (curl) { - expect(curl).toEqual(expected); - done(); - }); - }); - } - - multiReqCopyAsCurlTest( - 'start to end, with comment', - editorInput1, - { start: { row: 6 }, end: { row: 13 } }, - ` -curl -XGET "http://localhost:9200/_stats?level=shards" - -#in between comment - -curl -XPUT "http://localhost:9200/index_1/type1/1" -H 'Content-Type: application/json' -d' -{ - "f": 1 -}'`.trim() - ); -}); diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/setup_mocks.js b/src/legacy/core_plugins/console/public/quarantined/tests/src/setup_mocks.js deleted file mode 100644 index 203d8a20b10553..00000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/setup_mocks.js +++ /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. - */ -/* eslint no-undef: 0 */ -jest.mock('../../src/sense_editor/mode/worker', () => { - return { workerModule: { id: 'sense_editor/mode/worker', src: '' } }; -}); - -jest.mock('ui/chrome', () => { - return { - getInjected() { - return 'http://localhost:9200'; - } - }; -}); - -window.Worker = function () { - this.postMessage = () => {}; - this.terminate = () => {}; -}; -window.URL = { - createObjectURL: () => { - return ''; - }, -}; - -import 'brace'; -import 'brace/ext/language_tools'; -import 'brace/ext/searchbox'; -import 'brace/mode/json'; -import 'brace/mode/text'; - -jest.mock('../../../../np_ready/public/application', () => ({ legacyBackDoorToSettings: () => {}, })); - -document.queryCommandSupported = () => true; - -import jQuery from 'jquery'; -jest.spyOn(jQuery, 'ajax').mockImplementation( - () => - new Promise(() => { - // never resolve - }) -); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js b/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js index 91797f897d8ba4..e575b0f707aadd 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js @@ -28,7 +28,7 @@ import { ProxyConfigCollection } from '../proxy_config_collection'; describe('ProxyConfigCollection', function () { beforeEach(function () { - sinon.stub(fs, 'readFileSync').callsFake(() => new Buffer(0)); + sinon.stub(fs, 'readFileSync').callsFake(() => Buffer.alloc(0)); }); afterEach(function () { diff --git a/src/legacy/core_plugins/data/index.ts b/src/legacy/core_plugins/data/index.ts index 71f2fa5ffec7cf..c91500cd545d4b 100644 --- a/src/legacy/core_plugins/data/index.ts +++ b/src/legacy/core_plugins/data/index.ts @@ -20,7 +20,7 @@ import { resolve } from 'path'; import { Legacy } from '../../../../kibana'; import { mappings } from './mappings'; -import { SavedQuery } from './public'; +import { SavedQuery } from '../../../plugins/data/public'; // eslint-disable-next-line import/no-default-export export default function DataPlugin(kibana: any) { diff --git a/src/legacy/core_plugins/data/public/index.scss b/src/legacy/core_plugins/data/public/index.scss index 94f02fe2d60495..22877e217279f9 100644 --- a/src/legacy/core_plugins/data/public/index.scss +++ b/src/legacy/core_plugins/data/public/index.scss @@ -1,9 +1,3 @@ @import 'src/legacy/ui/public/styles/styling_constants'; -@import './query/query_bar/index'; - -@import 'src/plugins/data/public/ui/filter_bar/index'; - -@import 'src/plugins/data/public/ui/typeahead/index'; - -@import './search/search_bar/index'; +@import '../../../../plugins/data/public/index' diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 01f67a63ca9bec..481349463fc566 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -18,7 +18,7 @@ */ // /// Define plugin function -import { DataPlugin as Plugin, DataSetup, DataStart } from './plugin'; +import { DataPlugin as Plugin, DataStart } from './plugin'; export function plugin() { return new Plugin(); @@ -27,32 +27,17 @@ export function plugin() { // /// Export types & static code /** @public types */ -export { DataSetup, DataStart }; +export { DataStart }; +export { Field, FieldType, IFieldList, IndexPattern } from './index_patterns'; +export { SearchBar, SearchBarProps } from './search'; export { - Field, - FieldType, - FieldListInterface, - IndexPattern, - IndexPatterns, - StaticIndexPattern, -} from './index_patterns'; -export { QueryStringInput } from './query'; -export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './search'; + SavedQueryAttributes, + SavedQuery, + SavedQueryTimeFilter, +} from '../../../../plugins/data/public'; /** @public static code */ export * from '../common'; export { FilterStateManager } from './filter/filter_manager'; -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from './index_patterns'; +export { getFromSavedObject, getRoutes, flattenHitWrapper } from './index_patterns'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/errors.ts b/src/legacy/core_plugins/data/public/index_patterns/errors.ts deleted file mode 100644 index c64da47b8c7850..00000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/errors.ts +++ /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. - */ - -/* eslint-disable */ - -import { KbnError } from '../../../../../plugins/kibana_utils/public'; - -/** - * when a mapping already exists for a field the user is attempting to add - * @param {String} name - the field name - */ -export class IndexPatternAlreadyExists extends KbnError { - constructor(name: string) { - super(`An index pattern of "${name}" already exists`); - } -} - -/** - * Tried to call a method that relies on SearchSource having an indexPattern assigned - */ -export class IndexPatternMissingIndices extends KbnError { - constructor(message: string) { - const defaultMessage = "IndexPattern's configured pattern does not match any indices"; - - super( - message && message.length ? `No matching indices found: ${message}` : defaultMessage - ); - } -} - -/** - * Tried to call a method that relies on SearchSource having an indexPattern assigned - */ -export class NoDefinedIndexPatterns extends KbnError { - constructor() { - super('Define at least one index pattern to continue'); - } -} - -/** - * Tried to load a route besides management/kibana/index but you don't have a default index pattern! - */ -export class NoDefaultIndexPattern extends KbnError { - constructor() { - super('Please specify a default index pattern'); - } -} diff --git a/src/legacy/core_plugins/data/public/index_patterns/index.ts b/src/legacy/core_plugins/data/public/index_patterns/index.ts index 496c4ba7b5b6f1..96995c10e54695 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index.ts @@ -17,4 +17,17 @@ * under the License. */ -export * from './index_patterns_service'; +import { IFieldType, indexPatterns } from '../../../../../plugins/data/public'; + +const getFromSavedObject = indexPatterns.getFromSavedObject; +const getRoutes = indexPatterns.getRoutes; +const flattenHitWrapper = indexPatterns.flattenHitWrapper; + +export { getFromSavedObject, getRoutes, flattenHitWrapper }; +export { IFieldType as FieldType }; +export { + Field, + IFieldList, + IndexPattern, + IndexPatternsContract, +} from '../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts deleted file mode 100644 index db1ece78e7b4d1..00000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts +++ /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 { IndexPatternsService, IndexPatternsSetup } from '.'; -import { flattenHitWrapper } from './index_patterns'; - -type IndexPatternsServiceClientContract = PublicMethodsOf; - -const createSetupContractMock = () => { - // Legacy mock - must be removed before migrating to new platform. - // Included here because we only want to call `jest.mock` when somebody creates - // the mock for this contract. - jest.mock('ui/chrome'); - - const setupContract: jest.Mocked = { - FieldList: {} as any, - flattenHitWrapper: jest.fn().mockImplementation(flattenHitWrapper), - formatHitProvider: jest.fn(), - indexPatterns: jest.fn() as any, - __LEGACY: { - // For BWC we must temporarily export the class implementation of Field, - // which is only used externally by the Index Pattern UI. - FieldImpl: jest.fn(), - }, - }; - - return setupContract; -}; - -const createMock = () => { - const mocked: jest.Mocked = { - setup: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - }; - - mocked.setup.mockReturnValue(createSetupContractMock()); - return mocked; -}; - -export const indexPatternsServiceMock = { - create: createMock, - createSetupContract: createSetupContractMock, - createStartContract: createSetupContractMock, -}; 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 deleted file mode 100644 index 381cd491f02103..00000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.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. - */ - -import { - UiSettingsClientContract, - SavedObjectsClientContract, - HttpServiceBase, - NotificationsStart, -} from 'src/core/public'; -import { FieldFormatsStart } from '../../../../../plugins/data/public'; -import { Field, FieldList, FieldListInterface, FieldType } from './fields'; -import { setNotifications, setFieldFormats } from './services'; - -import { - createFlattenHitWrapper, - formatHitProvider, - IndexPattern, - IndexPatterns, - StaticIndexPattern, -} from './index_patterns'; - -export interface IndexPatternDependencies { - uiSettings: UiSettingsClientContract; - savedObjectsClient: SavedObjectsClientContract; - http: HttpServiceBase; - notifications: NotificationsStart; - fieldFormats: FieldFormatsStart; -} - -/** - * Index Patterns Service - * - * @internal - */ -export class IndexPatternsService { - private setupApi: any; - - public setup() { - this.setupApi = { - FieldList, - flattenHitWrapper: createFlattenHitWrapper(), - formatHitProvider, - __LEGACY: { - // For BWC we must temporarily export the class implementation of Field, - // which is only used externally by the Index Pattern UI. - FieldImpl: Field, - }, - }; - return this.setupApi; - } - - public start({ - uiSettings, - savedObjectsClient, - http, - notifications, - fieldFormats, - }: IndexPatternDependencies) { - setNotifications(notifications); - setFieldFormats(fieldFormats); - - return { - ...this.setupApi, - indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http), - }; - } - - public stop() { - // nothing to do here yet - } -} - -// static code - -/** @public */ -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - validateIndexPattern, -} from './utils'; - -/** @public */ -export { - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from './errors'; - -// types - -/** @internal */ -export type IndexPatternsSetup = ReturnType; -export type IndexPatternsStart = ReturnType; - -/** @public */ -export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType, FieldListInterface }; - -/** @public */ -export { findIndexPatternByTitle } from './utils'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/services.ts b/src/legacy/core_plugins/data/public/index_patterns/services.ts deleted file mode 100644 index ecd898b28de636..00000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/services.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. - */ - -import { NotificationsStart } from 'src/core/public'; -import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; -import { FieldFormatsStart } from '../../../../../plugins/data/public'; - -export const [getNotifications, setNotifications] = createGetterSetter( - 'Notifications' -); - -export const [getFieldFormats, setFieldFormats] = createGetterSetter( - 'FieldFormats' -); diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts deleted file mode 100644 index cff48144489f05..00000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.test.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. - */ - -import { - CONTAINS_SPACES, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - validateIndexPattern, -} from './utils'; - -describe('Index Pattern Utils', () => { - describe('Validation', () => { - it('should not allow space in the pattern', () => { - const errors = validateIndexPattern('my pattern'); - expect(errors[CONTAINS_SPACES]).toBe(true); - }); - - it('should not allow illegal characters', () => { - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { - const errors = validateIndexPattern(`pattern${char}`); - expect(errors[ILLEGAL_CHARACTERS]).toEqual([char]); - }); - }); - - it('should return empty object when there are no errors', () => { - expect(validateIndexPattern('my-pattern-*')).toEqual({}); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.ts deleted file mode 100644 index 8c2878a3ff9bad..00000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.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 { find, get } from 'lodash'; - -import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; - -export const ILLEGAL_CHARACTERS = 'ILLEGAL_CHARACTERS'; -export const CONTAINS_SPACES = 'CONTAINS_SPACES'; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|']; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.concat( - ' ' -); - -function findIllegalCharacters(indexPattern: string): string[] { - const illegalCharacters = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.reduce( - (chars: string[], char: string) => { - if (indexPattern.includes(char)) { - chars.push(char); - } - return chars; - }, - [] - ); - - return illegalCharacters; -} - -/** - * Returns an object matching a given title - * - * @param client {SavedObjectsClientContract} - * @param title {string} - * @returns {Promise} - */ -export async function findIndexPatternByTitle( - client: SavedObjectsClientContract, - title: string -): Promise | void> { - if (!title) { - return Promise.resolve(); - } - - const { savedObjects } = await client.find({ - type: 'index-pattern', - perPage: 10, - search: `"${title}"`, - searchFields: ['title'], - fields: ['title'], - }); - - return find( - savedObjects, - (obj: SimpleSavedObject) => obj.get('title').toLowerCase() === title.toLowerCase() - ); -} - -function indexPatternContainsSpaces(indexPattern: string): boolean { - return indexPattern.includes(' '); -} - -export function validateIndexPattern(indexPattern: string) { - const errors: Record = {}; - - const illegalCharacters = findIllegalCharacters(indexPattern); - - if (illegalCharacters.length) { - errors[ILLEGAL_CHARACTERS] = illegalCharacters; - } - - if (indexPatternContainsSpaces(indexPattern)) { - errors[CONTAINS_SPACES] = true; - } - - return errors; -} - -export function getFromSavedObject(savedObject: any) { - if (get(savedObject, 'attributes.fields') === undefined) { - return; - } - - return { - id: savedObject.id, - fields: JSON.parse(savedObject.attributes.fields), - title: savedObject.attributes.title, - }; -} - -export function getRoutes() { - return { - edit: '/management/kibana/index_patterns/{{id}}', - addField: '/management/kibana/index_patterns/{{id}}/create-field', - indexedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:indexedFields)', - scriptedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:scriptedFields)', - sourceFilters: '/management/kibana/index_patterns/{{id}}?_a=(tab:sourceFilters)', - }; -} diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts index b1d838aed992da..a6646ea338c93c 100644 --- a/src/legacy/core_plugins/data/public/legacy.ts +++ b/src/legacy/core_plugins/data/public/legacy.ts @@ -43,5 +43,4 @@ export const setup = dataPlugin.setup(npSetup.core); export const start = dataPlugin.start(npStart.core, { data: npStart.plugins.data, - uiActions: npSetup.plugins.uiActions, }); diff --git a/src/legacy/core_plugins/data/public/mocks.ts b/src/legacy/core_plugins/data/public/mocks.ts deleted file mode 100644 index 39d1296ddf8bc4..00000000000000 --- a/src/legacy/core_plugins/data/public/mocks.ts +++ /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 { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock'; - -function createDataSetupMock() { - return { - indexPatterns: indexPatternsServiceMock.createSetupContract(), - }; -} - -function createDataStartMock() { - return {}; -} - -export const dataPluginMock = { - createSetup: createDataSetupMock, - createStart: createDataStartMock, -}; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index da24576655d2b3..a4fdfd7482f741 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -18,30 +18,16 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search'; -import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns'; +import { createSearchBar, StatetfulSearchBarProps } from './search'; 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, - GLOBAL_APPLY_FILTER_ACTION, -} from './filter/action/apply_filter_action'; -import { APPLY_FILTER_TRIGGER } from '../../../../plugins/embeddable/public'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../plugins/data/public/services'; export interface DataPluginStartDependencies { data: DataPublicPluginStart; - uiActions: IUiActionsSetup; -} - -/** - * Interface for this plugin's returned `setup` contract. - * - * @public - */ -export interface DataSetup { - indexPatterns: IndexPatternsSetup; } /** @@ -50,8 +36,6 @@ export interface DataSetup { * @public */ export interface DataStart { - indexPatterns: IndexPatternsStart; - search: SearchStart; ui: { SearchBar: React.ComponentType; }; @@ -69,35 +53,17 @@ export interface DataStart { * or static code. */ -export class DataPlugin implements Plugin { - private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); - private readonly search: SearchService = new SearchService(); - - private setupApi!: DataSetup; +export class DataPlugin implements Plugin { private storage!: IStorageWrapper; - public setup(core: CoreSetup): DataSetup { + public setup(core: CoreSetup) { this.storage = new Storage(window.localStorage); - - this.setupApi = { - indexPatterns: this.indexPatterns.setup(), - }; - - return this.setupApi; } - public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { - const { uiSettings, http, notifications, savedObjects } = core; - - const indexPatternsService = this.indexPatterns.start({ - uiSettings, - savedObjectsClient: savedObjects.client, - http, - notifications, - fieldFormats: data.fieldFormats, - }); - - initLegacyModule(indexPatternsService.indexPatterns); + public start(core: CoreStart, { data }: DataPluginStartDependencies): DataStart { + // This is required for when Angular code uses Field and FieldList. + setFieldFormats(data.fieldFormats); + initLegacyModule(data.indexPatterns); const SearchBar = createSearchBar({ core, @@ -105,29 +71,12 @@ export class DataPlugin implements Plugin { return { FilterBar: () =>
, - }; -}); - -jest.mock('../../../../../data/public', () => { - return { - QueryStringInput: () =>
, - }; -}); - -jest.mock('../../../query/query_bar', () => { - return { QueryBarTopRow: () =>
, }; }); @@ -105,6 +94,11 @@ function wrapSearchBarInContext(testProps: any) { notifications: startMock.notifications, http: startMock.http, storage: createMockStorage(), + data: { + query: { + savedQueries: {}, + }, + }, }; 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 6a1ef77a566538..f547fada4a3b13 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 @@ -24,13 +24,6 @@ import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; -import { IndexPattern } from '../../../../../data/public'; -import { QueryBarTopRow } from '../../../query'; -import { SavedQuery, SavedQueryAttributes } from '../index'; -import { SavedQueryMeta, SaveQueryForm } from './saved_query_management/save_query_form'; -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 { withKibana, KibanaReactContextValue, @@ -40,8 +33,15 @@ import { TimeRange, Query, esFilters, + IIndexPattern, TimeHistoryContract, FilterBar, + SavedQuery, + SavedQueryAttributes, + SavedQueryMeta, + SaveQueryForm, + SavedQueryManagementComponent, + QueryBarTopRow, } from '../../../../../../../plugins/data/public'; interface SearchBarInjectedDeps { @@ -61,7 +61,7 @@ interface SearchBarInjectedDeps { } export interface SearchBarOwnProps { - indexPatterns?: IndexPattern[]; + indexPatterns?: IIndexPattern[]; isLoading?: boolean; customSubmitButton?: React.ReactNode; screenTitle?: string; @@ -110,8 +110,8 @@ class SearchBarUI extends Component { showAutoRefreshOnly: false, }; - private savedQueryService!: SavedQueryService; private services = this.props.kibana.services; + private savedQueryService = this.services.data.query.savedQueries; public filterBarRef: Element | null = null; public filterBarWrapperRef: Element | null = null; @@ -366,9 +366,6 @@ class SearchBarUI extends Component { this.setFilterBarHeight(); this.ro.observe(this.filterBarRef); } - if (this.services.savedObjects) { - this.savedQueryService = createSavedQueryService(this.services.savedObjects.client); - } } public componentDidUpdate() { 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 f369bf997c1a91..faf6e24aa6ed5d 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,23 +17,4 @@ * under the License. */ -import { RefreshInterval, TimeRange, Query, esFilters } from 'src/plugins/data/public'; - export * from './components'; - -export type SavedQueryTimeFilter = TimeRange & { - refreshInterval: RefreshInterval; -}; - -export interface SavedQuery { - id: string; - attributes: SavedQueryAttributes; -} - -export interface SavedQueryAttributes { - title: string; - description: string; - query: Query; - filters?: esFilters.Filter[]; - timefilter?: SavedQueryTimeFilter; -} diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/legacy/core_plugins/data/public/search/search_service.ts deleted file mode 100644 index 90ac288912f642..00000000000000 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ /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 { SavedObjectsClientContract } from 'src/core/public'; -import { createSavedQueryService } from './search_bar/lib/saved_query_service'; - -/** - * Search Service - * @internal - */ - -export class SearchService { - public setup() { - // Service requires index patterns, which are only available in `start` - } - - public start(savedObjectsClient: SavedObjectsClientContract) { - return { - services: { - savedQueryService: createSavedQueryService(savedObjectsClient), - }, - }; - } - - public stop() {} -} - -/** @public */ - -export type SearchStart = ReturnType; 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 06c5caa04ba9ac..1916097b0335c4 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -21,9 +21,9 @@ import { once } from 'lodash'; // @ts-ignore import { uiModules } from 'ui/modules'; -import { IndexPatterns } from '../index_patterns/index_patterns'; +import { IndexPatternsContract } from 'src/plugins/data/public'; /** @internal */ -export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { +export const initLegacyModule = once((indexPatterns: IndexPatternsContract): void => { uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js index 6d4ce31d453cc7..d5f58236027a96 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js @@ -23,7 +23,7 @@ import { ControlEditor } from './control_editor'; import { addControl, moveControl, newControl, removeControl, setControl } from '../../editor_utils'; import { getLineageMap, getParentCandidates } from '../../lineage'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { start as data } from '../../../../../core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { EuiButton, @@ -41,7 +41,7 @@ class ControlsTabUi extends Component { } getIndexPattern = async (indexPatternId) => { - return await data.indexPatterns.indexPatterns.get(indexPatternId); + return await npStart.plugins.data.indexPatterns.get(indexPatternId); } onChange = value => this.props.setValue('controls', value) diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js index 11c6f27af38c5f..ce7d48a3f13762 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js @@ -49,7 +49,7 @@ class FieldSelectUi extends Component { this.loadFields(this.state.indexPatternId); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (this.props.indexPatternId !== nextProps.indexPatternId) { this.loadFields(nextProps.indexPatternId); } 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 86fe6db9b0778e..e1dadff4a9ae74 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 @@ -28,7 +28,6 @@ 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'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators @@ -173,7 +172,7 @@ class ListControl extends Control { export async function listControlFactory(controlParams, useTimeFilter, SearchSource) { let indexPattern; try { - indexPattern = await data.indexPatterns.indexPatterns.get(controlParams.indexPattern); + indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); // dynamic options are only allowed on String fields but the setting defaults to true so it could // be enabled for non-string fields (since UI input is hidden for non-string fields). 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 b40a9f8e6efd40..c5f6de1c530974 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 @@ -40,27 +40,20 @@ jest.mock('ui/new_platform', () => ({ getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } + }, + indexPatterns: { + get: () => ({ + fields: { getByName: name => { + const fields = { myField: { name: 'myField' } }; + return fields[name]; + } } + }), } } }, }, })); -jest.mock('../../../../core_plugins/data/public/legacy', () => ({ - start: { - indexPatterns: { - indexPatterns: { - get: () => ({ - fields: { getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - } } - }), - } - }, - } -})); - chrome.getInjected.mockImplementation((key) => { switch(key) { case 'autocompleteTimeout': return 1000; 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 2a05a1224aab98..d2b1aff78ac2b8 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 @@ -26,7 +26,6 @@ import { 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) => { @@ -103,7 +102,7 @@ class RangeControl extends Control { export async function rangeControlFactory(controlParams, useTimeFilter, SearchSource) { let indexPattern; try { - indexPattern = await data.indexPatterns.indexPatterns.get(controlParams.indexPattern); + indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); } catch (err) { // ignore not found error and return control so it can be displayed in disabled state. } 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 3e6d6a49a11184..eaefcda4140fcb 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 @@ -48,27 +48,20 @@ jest.mock('ui/new_platform', () => ({ getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } + }, + indexPatterns: { + get: () => ({ + fields: { getByName: name => { + const fields = { myNumberField: { name: 'myNumberField' } }; + return fields[name]; + } + } }), } } }, }, })); -jest.mock('../../../../core_plugins/data/public/legacy', () => ({ - start: { - indexPatterns: { - indexPatterns: { - get: () => ({ - fields: { getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - } - } }), - } - }, - } -})); - describe('fetch', () => { const controlParams = { id: '1', 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 e341b7c4a5a866..bad006aa8c7d5a 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 @@ -21,7 +21,7 @@ import { mount } from 'enzyme'; import { IndexPattern } from 'ui/index_patterns'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { flattenHitWrapper } from '../../../../data/public/index_patterns/index_patterns/flatten_hit'; +import { flattenHitWrapper } from '../../../../data/public/'; import { DocViewTable } from './table'; // @ts-ignore diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js index 319f7d9b9fa9f9..014606fb375ab3 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js @@ -19,9 +19,12 @@ import $ from 'jquery'; -import { CUSTOM_LEGEND_VIS_TYPES } from '../../../ui/public/vis/vis_types/vislib_vis_legend'; +import React from 'react'; + +import { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from '../../../ui/public/vis/vis_types/vislib_vis_legend'; import { VislibVisProvider } from '../../../ui/public/vislib/vis'; import chrome from '../../../ui/public/chrome'; +import { mountReactNode } from '../../../../core/public/utils'; const legendClassName = { top: 'visLib--legend-top', @@ -30,24 +33,30 @@ const legendClassName = { right: 'visLib--legend-right', }; - export class vislibVisController { constructor(el, vis) { this.el = el; this.vis = vis; - this.$scope = null; + this.unmount = null; + this.legendRef = React.createRef(); + // vis mount point this.container = document.createElement('div'); this.container.className = 'visLib'; this.el.appendChild(this.container); + // chart mount point this.chartEl = document.createElement('div'); this.chartEl.className = 'visLib__chart'; this.container.appendChild(this.chartEl); + // legend mount point + this.legendEl = document.createElement('div'); + this.legendEl.className = 'visLib__legend'; + this.container.appendChild(this.legendEl); } render(esResponse, visParams) { - if (this.vis.vislibVis) { + if (this.vislibVis) { this.destroy(); } @@ -56,62 +65,69 @@ export class vislibVisController { const $injector = await chrome.dangerouslyGetActiveInjector(); const Private = $injector.get('Private'); this.Vislib = Private(VislibVisProvider); - this.$compile = $injector.get('$compile'); - this.$rootScope = $injector.get('$rootScope'); } if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { return resolve(); } - this.vis.vislibVis = new this.Vislib(this.chartEl, visParams); - this.vis.vislibVis.on('brush', this.vis.API.events.brush); - this.vis.vislibVis.on('click', this.vis.API.events.filter); - this.vis.vislibVis.on('renderComplete', resolve); + this.vislibVis = new this.Vislib(this.chartEl, visParams); + this.vislibVis.on('brush', this.vis.API.events.brush); + this.vislibVis.on('click', this.vis.API.events.filter); + this.vislibVis.on('renderComplete', resolve); - this.vis.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); + this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); if (visParams.addLegend) { $(this.container).attr('class', (i, cls) => { return cls.replace(/visLib--legend-\S+/g, ''); }).addClass(legendClassName[visParams.legendPosition]); - this.$scope = this.$rootScope.$new(); - this.$scope.refreshLegend = 0; - this.$scope.vis = this.vis; - this.$scope.visData = esResponse; - this.$scope.visParams = visParams; - this.$scope.uiState = this.$scope.vis.getUiState(); - const legendHtml = this.$compile('')(this.$scope); - this.container.appendChild(legendHtml[0]); - this.$scope.$digest(); + this.mountLegend(esResponse, visParams.legendPosition); } - this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + this.vislibVis.render(esResponse, this.vis.getUiState()); // refreshing the legend after the chart is rendered. // this is necessary because some visualizations // provide data necessary for the legend only after a render cycle. - if (visParams.addLegend && CUSTOM_LEGEND_VIS_TYPES.includes(this.vis.vislibVis.visConfigArgs.type)) { - this.$scope.refreshLegend++; - this.$scope.$digest(); - - this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + if (visParams.addLegend && CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type)) { + this.unmountLegend(); + this.mountLegend(esResponse, visParams.legendPosition); + this.vislibVis.render(esResponse, this.vis.getUiState()); } }); } + mountLegend(visData, position) { + this.unmount = mountReactNode( + + )(this.legendEl); + } + + unmountLegend() { + if (this.unmount) { + this.unmount(); + } + } + destroy() { - if (this.vis.vislibVis) { - this.vis.vislibVis.off('brush', this.vis.API.events.brush); - this.vis.vislibVis.off('click', this.vis.API.events.filter); - this.vis.vislibVis.destroy(); - delete this.vis.vislibVis; + if (this.unmount) { + this.unmount(); } - $(this.container).find('vislib-legend').remove(); - if (this.$scope) { - this.$scope.$destroy(); - this.$scope = null; + + if (this.vislibVis) { + this.vislibVis.off('brush', this.vis.API.events.brush); + this.vislibVis.off('click', this.vis.API.events.filter); + this.vislibVis.destroy(); + delete this.vislibVis; } } } diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 91364071579abb..659ca36d840906 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -23,8 +23,6 @@ import { promisify } from 'util'; import { migrations } from './migrations'; import manageUuid from './server/lib/manage_uuid'; -import { searchApi } from './server/routes/api/search'; -import { scrollSearchApi } from './server/routes/api/scroll_search'; import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; import { homeApi } from './server/routes/api/home'; @@ -62,11 +60,12 @@ export default function (kibana) { uiExports: { hacks: [ + 'plugins/kibana/discover', 'plugins/kibana/dev_tools', + 'plugins/kibana/visualize', ], savedObjectTypes: [ 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register', - 'plugins/kibana/discover/saved_searches/saved_search_register', 'plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register', ], app: { @@ -328,9 +327,7 @@ export default function (kibana) { // uuid await manageUuid(server); // routes - searchApi(server); scriptsApi(server); - scrollSearchApi(server); importApi(server); exportApi(server); homeApi(server); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index 07e4173d5323fb..8410040a0100d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -193,78 +193,160 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to add a visualization to the dashboard. - -

-

- - visit the Visualize app - , + "maxWidth": "36em", } } > - If you haven't set up any visualizations yet, - - visit the Visualize app - - to create your first visualization - -

+
+ + +
+ +
+ @@ -464,51 +546,119 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to start working on your new dashboard. - -

+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to start working on your new dashboard. +

+
+
+
+ + +
+ +
+ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index a4604d17ddecda..69bdcf59bb2274 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; describe('DashboardEmptyScreen', () => { const defaultProps = { @@ -26,7 +28,7 @@ describe('DashboardEmptyScreen', () => { onLinkClick: jest.fn(), }; - function mountComponent(props?: Props) { + function mountComponent(props?: DashboardEmptyScreenProps) { const compProps = props || defaultProps; const comp = mountWithIntl(); return comp; @@ -35,14 +37,14 @@ describe('DashboardEmptyScreen', () => { test('renders correctly with visualize paragraph', () => { const component = mountComponent(); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(1); }); test('renders correctly without visualize paragraph', () => { const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } }); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index 14c35759d70a99..d9eadf6c0e37d0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,9 +6,5 @@ .dshStartScreen { text-align: center; - padding: $euiSize; - - > * { - max-width: 36em !important; - } + padding: $euiSizeS; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 9c50adeeefccbd..ef1bcab589c4a0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -26,7 +26,7 @@ import { ChromeStart, LegacyCoreStart, SavedObjectsClientContract, - UiSettingsClientContract, + IUiSettingsClient, } from 'kibana/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { @@ -47,7 +47,6 @@ import { // @ts-ignore import { initDashboardApp } from './legacy_app'; -import { DataStart } from '../../../data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; @@ -55,8 +54,6 @@ import { SharePluginStart } from '../../../../../plugins/share/public'; export interface RenderDeps { core: LegacyCoreStart; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; - dataStart: DataStart; npDataStart: NpDataStart; navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; @@ -64,10 +61,10 @@ export interface RenderDeps { dashboardConfig: any; savedDashboards: any; dashboardCapabilities: any; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; chrome: ChromeStart; addBasePath: (path: string) => string; - savedQueryService: DataStart['search']['services']['savedQueryService']; + savedQueryService: NpDataStart['query']['savedQueries']; embeddables: IEmbeddableStart; localStorage: Storage; share: SharePluginStart; @@ -134,7 +131,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/State', 'app/dashboard/ConfirmModal', 'app/dashboard/icon', - 'app/dashboard/emptyScreen', ]); return dashboardAngularModule; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 0b842fbfaeddc5..3cf8932958b6d8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -12,7 +12,6 @@ show-filter-bar="showFilterBar()" show-save-query="showSaveQuery" - filters="model.filters" query="model.query" saved-query="savedQuery" screen-title="screenTitle" @@ -36,7 +35,7 @@ --> -
-
-

-
- -
- -
- -
- -
-
-

{{screenTitle}}

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 0ce8f2ef59fc0c..04a8e68276fc20 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,7 +17,6 @@ * under the License. */ -import { StaticIndexPattern, SavedQuery } from 'plugins/data'; import moment from 'moment'; import { Subscription } from 'rxjs'; @@ -31,7 +30,13 @@ import { import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; -import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public'; +import { + IIndexPattern, + TimeRange, + Query, + esFilters, + SavedQuery, +} from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; import { RenderDeps } from './application'; @@ -54,7 +59,7 @@ export interface DashboardAppScope extends ng.IScope { savedQuery?: SavedQuery; refreshInterval: any; panels: SavedDashboardPanel[]; - indexPatterns: StaticIndexPattern[]; + indexPatterns: IIndexPattern[]; $evalAsync: any; dashboardViewMode: ViewMode; expandedPanel?: string; @@ -113,6 +118,7 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { AppStateClass: AppState, config, confirmModal, + indexPatterns: deps.npDataStart.indexPatterns, ...deps, }), }; 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 1a0e13417d1e1f..fd49b26e0d948c 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 @@ -21,9 +21,10 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; +import { uniq, noop } from 'lodash'; import { Subscription } from 'rxjs'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { subscribeWithScope, @@ -35,13 +36,10 @@ import { AppStateClass as TAppStateClass, KbnUrl, SaveOptions, - SavedObjectFinder, unhashUrl, } from './legacy_imports'; -import { FilterStateManager, IndexPattern, SavedQuery } from '../../../data/public'; -import { Query } from '../../../../../plugins/data/public'; - -import './dashboard_empty_screen_directive'; +import { FilterStateManager, IndexPattern } from '../../../data/public'; +import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; import { DashboardContainer, @@ -71,6 +69,10 @@ import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { RenderDeps } from './application'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../plugins/kibana_react/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; @@ -78,9 +80,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $routeParams: any; getAppState: any; globalState: State; - indexPatterns: { - getDefault: () => Promise; - }; + indexPatterns: IndexPatternsContract; dashboardConfig: any; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; @@ -117,15 +117,11 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata }, + core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; - function getUnhashableStates(): State[] { - return [getAppState(), globalState].filter(Boolean); - } - let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); @@ -149,6 +145,16 @@ export class DashboardAppController { } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; + $scope.getShouldShowEditHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsEditMode() && + !dashboardConfig.getHideWriteControls(); + + $scope.getShouldShowViewHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsViewMode() && + !dashboardConfig.getHideWriteControls(); + const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { return; @@ -171,12 +177,23 @@ export class DashboardAppController { } else { indexPatterns.getDefault().then(defaultIndexPattern => { $scope.$evalAsync(() => { - $scope.indexPatterns = [defaultIndexPattern]; + $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; }); }); } }; + const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => { + const emptyScreenProps: DashboardEmptyScreenProps = { + onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, + showLinkToVisualize: shouldShowEditHelp, + }; + if (shouldShowEditHelp) { + emptyScreenProps.onVisualizeClick = noop; + } + return emptyScreenProps; + }; + const getDashboardInput = (): DashboardContainerInput => { const embeddablesMap: { [key: string]: DashboardPanelState; @@ -188,6 +205,8 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; } + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); return { id: dashboardStateManager.savedDashboard.id || '', filters: queryFilter.getFilters(), @@ -200,6 +219,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmptyState: shouldShowEditHelp || shouldShowViewHelp, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, title: dashboardStateManager.getTitle(), @@ -240,6 +260,15 @@ export class DashboardAppController { if (!isErrorEmbeddable(container)) { dashboardContainer = container; + dashboardContainer.renderEmpty = () => { + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); + const isEmptyState = shouldShowEditHelp || shouldShowViewHelp; + return isEmptyState ? ( + + ) : null; + }; + updateIndexPatterns(dashboardContainer); outputSubscription = dashboardContainer.getOutput$().subscribe(() => { @@ -340,15 +369,6 @@ export class DashboardAppController { updateBreadcrumbs(); dashboardStateManager.registerChangeListener(updateBreadcrumbs); - $scope.getShouldShowEditHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsEditMode() && - !dashboardConfig.getHideWriteControls(); - $scope.getShouldShowViewHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsViewMode() && - !dashboardConfig.getHideWriteControls(); - const getChangesFromAppStateForContainerState = () => { const appStateDashboardInput = getDashboardInput(); if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) { @@ -724,6 +744,10 @@ export class DashboardAppController { }; navActions[TopNavIds.ADD] = () => { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); + openAddPanelFlyout({ embeddable: dashboardContainer, getAllFactories: embeddables.getEmbeddableFactories, @@ -735,6 +759,8 @@ export class DashboardAppController { } }; + navActions[TopNavIds.VISUALIZE] = async () => {}; + navActions[TopNavIds.OPTIONS] = anchorElement => { showOptionsPopover({ anchorElement, @@ -753,7 +779,7 @@ export class DashboardAppController { anchorElement, allowEmbed: true, allowShortUrl: !dashboardConfig.getHideWriteControls(), - shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), + shareableUrl: unhashUrl(window.location.href), objectId: dash.id, objectType: 'dashboard', sharingData: { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js index 1abe8581c54c6f..3f764cf5766687 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js @@ -20,24 +20,26 @@ import { uiModules } from 'ui/modules'; import { capabilities } from 'ui/capabilities'; -uiModules.get('kibana') - .provider('dashboardConfig', () => { - let hideWriteControls = !capabilities.get().dashboard.showWriteControls; +export function dashboardConfigProvider() { + let hideWriteControls = !capabilities.get().dashboard.showWriteControls; + + return { + /** + * Part of the exposed plugin API - do not remove without careful consideration. + * @type {boolean} + */ + turnHideWriteControlsOn() { + hideWriteControls = true; + }, + $get() { + return { + getHideWriteControls() { + return hideWriteControls; + } + }; + } + }; +} - return { - /** - * Part of the exposed plugin API - do not remove without careful consideration. - * @type {boolean} - */ - turnHideWriteControlsOn() { - hideWriteControls = true; - }, - $get() { - return { - getHideWriteControls() { - return hideWriteControls; - } - }; - } - }; - }); +uiModules.get('kibana') + .provider('dashboardConfig', dashboardConfigProvider); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx index d5a4e6e6a325d9..234228ba4166a9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx @@ -18,29 +18,43 @@ */ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { + EuiIcon, + EuiLink, + EuiSpacer, + EuiPageContent, + EuiPageBody, + EuiPage, + EuiText, +} from '@elastic/eui'; import * as constants from './dashboard_empty_screen_constants'; -export interface Props { +export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; + onVisualizeClick?: () => void; } -export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) { +export function DashboardEmptyScreen({ + showLinkToVisualize, + onLinkClick, +}: DashboardEmptyScreenProps) { const linkToVisualizeParagraph = ( -

- - {constants.visualizeAppLinkTest} - - ), - }} - /> -

+ +

+ + {constants.visualizeAppLinkTest} + + ), + }} + /> +

+
); const paragraph = ( description1: string, @@ -50,15 +64,15 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props dataTestSubj?: string ) => { return ( -

- + +

{description1} {linkText} {description2} - -

+

+ ); }; const addVisualizationParagraph = ( @@ -70,6 +84,7 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props constants.addVisualizationLinkAriaLabel, 'emptyDashboardAddPanelButton' )} + {linkToVisualizeParagraph} ); @@ -81,11 +96,19 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props ); return ( - - -

{constants.fillDashboardTitle}

- {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
+ + + + + + +

{constants.fillDashboardTitle}

+
+ + {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} +
+
+
); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts deleted file mode 100644 index 5ebefd817ca4a7..00000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.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. - */ -// @ts-ignore -import angular from 'angular'; -import { DashboardEmptyScreen } from './dashboard_empty_screen'; - -angular - .module('app/dashboard/emptyScreen', ['react']) - .directive('dashboardEmptyScreen', function(reactDirective: any) { - return reactDirective(DashboardEmptyScreen, [ - ['showLinkToVisualize', { watchDepth: 'value' }], - ['onLinkClick', { watchDepth: 'reference' }], - ]); - }); 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 c236ac7843c038..8b786144c74205 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 @@ -18,7 +18,6 @@ */ import './np_core.test.mocks'; - import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; import { AppStateClass } from './legacy_imports'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js index c7f2adb4b875b9..7d60b80b59620c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js @@ -119,7 +119,7 @@ export function initDashboardApp(app, deps) { }, resolve: { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl).then(() => { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -154,7 +154,7 @@ export function initDashboardApp(app, deps) { requireUICapability: 'dashboard.createNew', resolve: { dash: function (redirectWhenMissing, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(); }) @@ -174,7 +174,7 @@ export function initDashboardApp(app, deps) { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { const id = $route.current.params.id; - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(id); }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index 7c3c389330887e..af0a833399a523 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -63,6 +63,5 @@ export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule } from 'ui/legacy_compat'; export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; -export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 609bd717f3c485..d2b45fde4727e4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -64,7 +64,6 @@ export interface DashboardPluginSetupDependencies { export class DashboardPlugin implements Plugin { private startDependencies: { - dataStart: DataStart; npDataStart: NpDataStart; savedObjectsClient: SavedObjectsClientContract; embeddables: IEmbeddableStart; @@ -84,7 +83,6 @@ export class DashboardPlugin implements Plugin { throw new Error('not started yet'); } const { - dataStart, savedObjectsClient, embeddables, navigation, @@ -96,15 +94,13 @@ export class DashboardPlugin implements Plugin { core: contextCore as LegacyCoreStart, ...angularDependencies, navigation, - dataStart, share, npDataStart, - indexPatterns: dataStart.indexPatterns.indexPatterns, savedObjectsClient, chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, uiSettings: contextCore.uiSettings, - savedQueryService: dataStart.search.services.savedQueryService, + savedQueryService: npDataStart.query.savedQueries, embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), @@ -136,7 +132,6 @@ export class DashboardPlugin implements Plugin { { data: dataStart, embeddables, navigation, npData, share }: DashboardPluginStartDependencies ) { this.startDependencies = { - dataStart, npDataStart: npData, savedObjectsClient, embeddables, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts index 9df86f2ca3ccee..c67d6891c18e70 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts @@ -26,4 +26,5 @@ export const TopNavIds = { ENTER_EDIT_MODE: 'enterEditMode', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', + VISUALIZE: 'visualize', }; 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 9ac76bfcfe04e2..92df04c536e438 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,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import '../../components/field_chooser/discover_field'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; // Load the kibana app dependencies. @@ -32,8 +32,9 @@ describe('discoverField', function () { let $scope; let indexPattern; let $elem; - - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach(ngMock.inject(function (Private, $rootScope, $compile) { $elem = angular.element(` pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach(ngMock.inject(function (Private) { indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); })); 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 bac56f008233c0..34c6483349af60 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,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import expect from '@kbn/expect'; import $ from 'jquery'; -import '../../components/field_chooser/field_chooser'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import FixturesHitsProvider from 'fixtures/hits'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { SimpleSavedObject } from '../../../../../../../core/public'; @@ -70,8 +70,10 @@ describe('discover field chooser directives', function () { on-remove-field="removeField" > `); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('kibana', ($provide) => { + beforeEach(ngMock.module('app/discover', ($provide) => { $provide.decorator('config', ($delegate) => { // disable shortDots for these tests $delegate.get = _.wrap($delegate.get, function (origGet, name) { diff --git a/src/legacy/core_plugins/kibana/public/discover/_discover.scss b/src/legacy/core_plugins/kibana/public/discover/_discover.scss index 12cac1c89275b3..0da28e41579aed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_discover.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_discover.scss @@ -1,5 +1,3 @@ -@import 'node_modules/@elastic/eui/src/components/panel/mixins'; - discover-app { flex-grow: 1; @@ -37,7 +35,7 @@ discover-app { z-index: 1; } -@include euiPanel('dscWrapper__content'); +@include euiPanel('.dscWrapper__content'); .dscWrapper__content { padding-top: $euiSizeXS; @@ -213,7 +211,6 @@ discover-app { } .dscResults { - h3 { margin: -20px 0 10px 0; text-align: center; diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss index b311dd8a347789..0d70bb993fac10 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_index.scss @@ -20,7 +20,7 @@ @import 'embeddable/index'; // Doc Viewer -@import 'doc_viewer/index'; +@import 'components/doc_viewer/index'; // Context styles @import 'angular/context/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context.js b/src/legacy/core_plugins/kibana/public/discover/angular/context.js index 58d1626ca4b14d..989712a16b2501 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context.js @@ -19,12 +19,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices, subscribeWithScope } from './../kibana_services'; +import { getAngularModule, getServices, subscribeWithScope } from './../kibana_services'; import './context_app'; import contextAppRouteTemplate from './context.html'; -import { getRootBreadcrumbs } from '../breadcrumbs'; -const { FilterBarQueryFilterProvider, uiRoutes, chrome } = getServices(); +import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; +import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; +const { chrome } = getServices(); const k7Breadcrumbs = $route => { const { indexPattern } = $route.current.locals; @@ -44,26 +45,33 @@ const k7Breadcrumbs = $route => { ]; }; -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); +getAngularModule().config($routeProvider => { + $routeProvider + // deprecated route, kept for compatibility + // should be removed in the future + .when('/discover/context/:indexPatternId/:type/:id*', { + redirectTo: '/discover/context/:indexPatternId/:id', + }) + .when('/discover/context/:indexPatternId/:id*', { + controller: ContextAppRouteController, + k7Breadcrumbs, + controllerAs: 'contextAppRoute', + resolve: { + indexPattern: ($route, Promise) => { + const indexPattern = getServices().indexPatterns.get( + $route.current.params.indexPatternId + ); + return Promise.props({ ip: indexPattern }); + }, }, - }, - template: contextAppRouteTemplate, - }); + template: contextAppRouteTemplate, + }); +}); -function ContextAppRouteController($routeParams, $scope, AppState, config, indexPattern, Private) { - const queryFilter = Private(FilterBarQueryFilterProvider); +function ContextAppRouteController($routeParams, $scope, AppState, config, $route, getAppState, globalState) { + const filterManager = getServices().filterManager; + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); + const indexPattern = $route.current.locals.indexPattern.ip; this.state = new AppState(createDefaultAppState(config, indexPattern)); this.state.save(true); @@ -77,19 +85,20 @@ function ContextAppRouteController($routeParams, $scope, AppState, config, index () => this.state.save(true) ); - const updateSubsciption = subscribeWithScope($scope, queryFilter.getUpdates$(), { + const updateSubsciption = subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { - this.filters = _.cloneDeep(queryFilter.getFilters()); + this.filters = _.cloneDeep(filterManager.getFilters()); }, }); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', () => { + filterStateManager.destroy(); updateSubsciption.unsubscribe(); }); this.anchorId = $routeParams.id; this.indexPattern = indexPattern; this.discoverUrl = chrome.navLinks.get('kibana:discover').url; - this.filters = _.cloneDeep(queryFilter.getFilters()); + this.filters = _.cloneDeep(filterManager.getFilters()); } function createDefaultAppState(config, indexPattern) { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js index 4eb68c1bf50bcb..8c6e53974ba361 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js @@ -19,13 +19,15 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; import { fetchAnchorProvider } from '../anchor'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); describe('function fetchAnchor', function () { let fetchAnchor; @@ -35,11 +37,11 @@ describe('context app', function () { $provide.value('indexPatterns', createIndexPatternsStub()); })); - beforeEach(ngMock.inject(function createPrivateStubs(Private) { + beforeEach(ngMock.inject(function createPrivateStubs() { searchSourceStub = createSearchSourceStub([ { _id: 'hit1' }, ]); - fetchAnchor = Private(fetchAnchorProvider); + fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); })); afterEach(() => { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js index ea6a8c092e242b..a21e2117d1db6f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; @@ -33,7 +34,8 @@ const ANCHOR_TIMESTAMP_1000 = (new Date(MS_PER_DAY * 1000)).toJSON(); const ANCHOR_TIMESTAMP_3000 = (new Date(MS_PER_DAY * 3000)).toJSON(); describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); describe('function fetchPredecessors', function () { let fetchPredecessors; @@ -43,7 +45,7 @@ describe('context app', function () { $provide.value('indexPatterns', createIndexPatternsStub()); })); - beforeEach(ngMock.inject(function createPrivateStubs(Private) { + beforeEach(ngMock.inject(function createPrivateStubs() { searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); fetchPredecessors = (indexPatternId, timeField, sortDir, timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { const anchor = { @@ -53,7 +55,7 @@ describe('context app', function () { sort: [timeValNr, tieBreakerValue] }; - return Private(fetchContextProvider).fetchSurroundingDocs( + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( 'predecessors', indexPatternId, anchor, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js index 486c8ed9b410eb..145de081f0d443 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; @@ -32,17 +33,14 @@ const ANCHOR_TIMESTAMP_3 = (new Date(MS_PER_DAY * 3)).toJSON(); const ANCHOR_TIMESTAMP_3000 = (new Date(MS_PER_DAY * 3000)).toJSON(); describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); describe('function fetchSuccessors', function () { let fetchSuccessors; let searchSourceStub; - beforeEach(ngMock.module(function createServiceStubs($provide) { - $provide.value('indexPatterns', createIndexPatternsStub()); - })); - - beforeEach(ngMock.inject(function createPrivateStubs(Private) { + beforeEach(ngMock.inject(function createPrivateStubs() { searchSourceStub = createContextSearchSourceStub([], '@timestamp'); fetchSuccessors = (indexPatternId, timeField, sortDir, timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { @@ -53,7 +51,7 @@ describe('context app', function () { sort: [timeValNr, tieBreakerValue] }; - return Private(fetchContextProvider).fetchSurroundingDocs( + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( 'successors', indexPatternId, anchor, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js index 8c4cce810ca131..730b963d0474ef 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js @@ -19,17 +19,15 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../../../kibana_services'; -const { SearchSource } = getServices(); -export function fetchAnchorProvider(indexPatterns) { +export function fetchAnchorProvider(indexPatterns, searchSource) { return async function fetchAnchor( indexPatternId, anchorId, sort ) { const indexPattern = await indexPatterns.get(indexPatternId); - const searchSource = new SearchSource() + searchSource .setParent(undefined) .setField('index', indexPattern) .setField('version', true) diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts index 68ccf56594e723..fd71b7c49e8376 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts @@ -17,15 +17,14 @@ * under the License. */ -import { SortDirection } from '../../../../../../../ui/public/courier'; -import { IndexPatterns, IndexPattern, getServices } from '../../../kibana_services'; -import { reverseSortDir } from './utils/sorting'; +import { IndexPattern, SearchSource } 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'; +import { esFilters, IndexPatternsContract } from '../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { @@ -35,14 +34,12 @@ 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 const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map(days => days * DAY_MILLIS); -function fetchContextProvider(indexPatterns: IndexPatterns) { +function fetchContextProvider(indexPatterns: IndexPatternsContract) { return { fetchSurroundingDocs, }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts index 33f4454c18d400..eeae2aa2c5d0ae 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts @@ -16,10 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { reverseSortDir } from '../sorting'; -import { SortDirection } from '../../../../../../../../../ui/public/courier'; - -jest.mock('ui/new_platform'); +import { reverseSortDir, SortDirection } from '../sorting'; describe('function reverseSortDir', function() { test('reverse a given sort direction', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts index 47385aecb19373..4a0f531845f46e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts @@ -17,9 +17,13 @@ * under the License. */ -import { SortDirection } from '../../../../../../../../ui/public/courier'; import { IndexPattern } from '../../../../kibana_services'; +export enum SortDirection { + asc = 'asc', + desc = 'desc', +} + /** * The list of field names that are allowed for sorting, but not included in * index pattern fields. diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/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 index 579d9d95c6f719..55a378367392c4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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,9 @@ * specific language governing permissions and limitations * under the License. */ -import { getServices } from '../../../../kibana_services'; +import { getAngularModule, wrapInI18nContext } from '../../../../kibana_services'; import { ActionBar } from './action_bar'; -const { uiModules, wrapInI18nContext } = getServices(); - -uiModules.get('apps/context').directive('contextActionBar', function(reactDirective: any) { +getAngularModule().directive('contextActionBar', function(reactDirective: any) { return reactDirective(wrapInI18nContext(ActionBar)); }); 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 index b88e54379f448e..4a9480f9ea2ea5 100644 --- 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 @@ -20,22 +20,22 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { toastNotifications } from '../../../kibana_services'; +import { getServices, SearchSource } from '../../../kibana_services'; import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; -import { QueryParameterActionsProvider } from '../query_parameters'; +import { getQueryParameterActions } 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); +export function QueryActionsProvider(Promise) { + const fetchAnchor = fetchAnchorProvider(getServices().indexPatterns, new SearchSource()); + const { fetchSurroundingDocs } = fetchContextProvider(getServices().indexPatterns); const { setPredecessorCount, setQueryParameters, setSuccessorCount, - } = Private(QueryParameterActionsProvider); + } = getQueryParameterActions(); const setFailedStatus = (state) => (subject, details = {}) => ( state.loadingStatus[subject] = { @@ -79,7 +79,7 @@ export function QueryActionsProvider(Private, Promise) { }, (error) => { setFailedStatus(state)('anchor', { error }); - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.context.unableToLoadAnchorDocumentDescription', { defaultMessage: 'Unable to load the anchor document' }), @@ -128,7 +128,7 @@ export function QueryActionsProvider(Private, Promise) { }, (error) => { setFailedStatus(state)(type, { error }); - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.context.unableToLoadDocumentDescription', { defaultMessage: 'Unable to load documents' }), diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/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 index 5a445a65939eda..645ca32924ede8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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 @@ -19,15 +19,16 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { getServices } from '../../../../kibana_services'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; import { createIndexPatternsStub } from '../../api/__tests__/_stubs'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { npStart } from 'ui/new_platform'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); - + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); beforeEach(ngMock.module(function createServiceStubs($provide) { $provide.value('indexPatterns', createIndexPatternsStub()); })); @@ -35,9 +36,8 @@ describe('context app', function () { describe('action addFilter', function () { let addFilter; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - Private.stub(getServices().FilterBarQueryFilterProvider); - addFilter = Private(QueryParameterActionsProvider).addFilter; + beforeEach(ngMock.inject(function createPrivateStubs() { + addFilter = getQueryParameterActions().addFilter; })); it('should pass the given arguments to the filterManager', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/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 index 7c1fa320ae17bf..a8bef6fe75c79d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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 @@ -19,19 +19,21 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; - +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); describe('action setPredecessorCount', function () { let setPredecessorCount; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - setPredecessorCount = Private(QueryParameterActionsProvider).setPredecessorCount; + beforeEach(ngMock.inject(function createPrivateStubs() { + setPredecessorCount = getQueryParameterActions().setPredecessorCount; })); it('should set the predecessorCount to the given value', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/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 index 853c5726b3da52..a43a8a11a7bf82 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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 @@ -19,19 +19,22 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); describe('action setQueryParameters', function () { let setQueryParameters; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - setQueryParameters = Private(QueryParameterActionsProvider).setQueryParameters; + beforeEach(ngMock.inject(function createPrivateStubs() { + setQueryParameters = getQueryParameterActions().setQueryParameters; })); it('should update the queryParameters with valid properties from the given object', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/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 index d63bf2ecf53af8..4bbd462aaa4b08 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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 @@ -19,19 +19,22 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); describe('action setSuccessorCount', function () { let setSuccessorCount; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - setSuccessorCount = Private(QueryParameterActionsProvider).setSuccessorCount; + beforeEach(ngMock.inject(function createPrivateStubs() { + setSuccessorCount = getQueryParameterActions().setSuccessorCount; })); it('should set the successorCount to the given value', function () { 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 index 10fe6c0e2eda1f..28b35a1b81a7b3 100644 --- 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 @@ -18,8 +18,8 @@ */ import _ from 'lodash'; +import { getServices } from '../../../kibana_services'; import { generateFilters } from '../../../../../../../../plugins/data/public'; -import { npStart } from 'ui/new_platform'; import { MAX_CONTEXT_SIZE, @@ -28,8 +28,8 @@ import { } from './constants'; -export function QueryParameterActionsProvider(indexPatterns) { - const { filterManager } = npStart.plugins.data.query; +export function getQueryParameterActions() { + const filterManager = getServices().filterManager; const setPredecessorCount = (state) => (predecessorCount) => ( state.queryParameters.predecessorCount = clamp( @@ -62,7 +62,7 @@ export function QueryParameterActionsProvider(indexPatterns) { const indexPatternId = state.queryParameters.indexPatternId; const newFilters = generateFilters(filterManager, field, values, operation, indexPatternId); filterManager.addFilters(newFilters); - const indexPattern = await indexPatterns.get(indexPatternId); + const indexPattern = await getServices().indexPatterns.get(indexPatternId); indexPattern.popularizeField(field.name, 1); }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js index 3e7f47668df59a..14be90a3f61a43 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js @@ -17,7 +17,7 @@ * under the License. */ -export { QueryParameterActionsProvider } from './actions'; +export { getQueryParameterActions } from './actions'; export { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, 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 index c9856ad7949523..46093177123795 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js @@ -18,13 +18,13 @@ */ import _ from 'lodash'; -import { getServices, callAfterBindingsWorkaround } from './../kibana_services'; +import { getServices, callAfterBindingsWorkaround, getAngularModule } from './../kibana_services'; import contextAppTemplate from './context_app.html'; import './context/components/action_bar'; import { getFirstSortableField } from './context/api/utils/sorting'; import { createInitialQueryParametersState, - QueryParameterActionsProvider, + getQueryParameterActions, QUERY_PARAMETER_KEYS, } from './context/query_parameters'; import { @@ -34,17 +34,12 @@ import { QueryActionsProvider, } from './context/query'; -const { uiModules, timefilter } = getServices(); +const { timefilter } = getServices(); // load directives import '../../../../data/public/legacy'; -const module = uiModules.get('apps/context', [ - 'elasticsearch', - 'kibana', - 'kibana/config', - 'ngRoute', -]); +const module = getAngularModule(); module.directive('contextApp', function ContextApp() { return { @@ -67,7 +62,7 @@ module.directive('contextApp', function ContextApp() { }); function ContextAppController($scope, config, Private) { - const queryParameterActions = Private(QueryParameterActionsProvider); + const queryParameterActions = getQueryParameterActions(); const queryActions = Private(QueryActionsProvider); timefilter.disableAutoRefreshSelector(); 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 ab336396b5bed2..496e1cf375588e 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 @@ -70,7 +70,7 @@ export class DiscoverHistogram extends Component this.setState({ chartsTheme })); + .subscribe((chartsTheme: EuiChartThemeType['theme']) => this.setState({ chartsTheme })); } componentWillUnmount() { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js index b0b766478450fc..f1e783c56263e8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js @@ -23,10 +23,9 @@ import { DiscoverNoResults } from './no_results'; import { DiscoverUninitialized } from './uninitialized'; import { DiscoverUnsupportedIndexPattern } from './unsupported_index_pattern'; import { DiscoverHistogram } from './histogram'; -import { getServices } from '../../kibana_services'; +import { getAngularModule, wrapInI18nContext } from '../../kibana_services'; -const { wrapInI18nContext, uiModules } = getServices(); -const app = uiModules.get('apps/discover', ['react']); +const app = getAngularModule(); app.directive('discoverNoResults', reactDirective => reactDirective(wrapInI18nContext(DiscoverNoResults)) 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 ba74ea069c4ab7..ce1419cd637b93 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -23,7 +23,6 @@ import { Subscription } from 'rxjs'; import moment from 'moment'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; -import '../saved_searches/saved_searches'; import '../components/field_chooser/field_chooser'; // doc table @@ -33,7 +32,7 @@ import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_sour import * as columnActions from './doc_table/actions/columns'; import indexTemplate from './discover.html'; -import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; +import { showOpenSearchPanel } from '../components/top_nav/show_open_search_panel'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import '../components/fetch_error'; import { getPainlessError } from './get_painless_error'; @@ -43,7 +42,6 @@ import { getRequestInspectorStats, getResponseInspectorStats, getServices, - getUnhashableStatesProvider, hasSearchStategyForIndexPattern, intervalOptions, isDefaultTypeIndexPattern, @@ -57,27 +55,29 @@ import { vislibSeriesResponseHandlerProvider, Vis, SavedObjectSaveModal, + getAngularModule, ensureDefaultIndexPattern, } from '../kibana_services'; const { core, chrome, + data, docTitle, - FilterBarQueryFilterProvider, + filterManager, share, - StateProvider, timefilter, toastNotifications, - uiModules, - uiRoutes, -} = getServices(); + uiSettings +} = getServices(); -import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; -import { start as data } from '../../../../data/public/legacy'; +import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; import { generateFilters } from '../../../../../../plugins/data/public'; +import { getIndexPatternId } from '../helpers/get_index_pattern_id'; +import { registerTimefilterWithGlobalStateFactory } from '../../../../../ui/public/timefilter/setup_router'; +import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; -const { savedQueryService } = data.search.services; +const { getSavedQuery } = data.query.savedQueries; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -85,20 +85,20 @@ const fetchStatuses = { COMPLETE: 'complete', }; -const app = uiModules.get('apps/discover', [ - 'kibana/url', - 'kibana/index_patterns' -]); +const app = getAngularModule(); +app.run((globalState, $rootScope) => {registerTimefilterWithGlobalStateFactory( + timefilter, + globalState, + $rootScope +); +}); -uiRoutes - .defaults(/^\/discover(\/|$)/, { +app.config($routeProvider => { + const defaults = { + requireDefaultIndex: true, requireUICapability: 'discover.show', k7Breadcrumbs: ($route, $injector) => - $injector.invoke( - $route.current.params.id - ? getSavedSearchBreadcrumbs - : getRootBreadcrumbs - ), + $injector.invoke($route.current.params.id ? getSavedSearchBreadcrumbs : getRootBreadcrumbs), badge: uiCapabilities => { if (uiCapabilities.discover.save) { return undefined; @@ -111,21 +111,21 @@ uiRoutes tooltip: i18n.translate('kbn.discover.badge.readOnly.tooltip', { defaultMessage: 'Unable to save searches', }), - iconType: 'glasses' + iconType: 'glasses', }; - } - }) - .when('/discover/:id?', { + }, + }; + $routeProvider.when('/discover/:id?', { + ...defaults, template: indexTemplate, reloadOnSearch: false, resolve: { - savedObjects: function (Promise, indexPatterns, config, Private, $rootScope, kbnUrl, redirectWhenMissing, savedSearches, $route) { - const State = Private(StateProvider); + savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope, State) { + const indexPatterns = getServices().indexPatterns; const savedSearchId = $route.current.params.id; - - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(core, getServices().data, $rootScope, kbnUrl).then(() => { return Promise.props({ - ip: indexPatterns.getCache().then((savedObjects) => { + ip: indexPatterns.getCache().then((indexPatternList) => { /** * In making the indexPattern modifiable it was placed in appState. Unfortunately, * the load order of AppState conflicts with the load order of many other things @@ -136,20 +136,16 @@ uiRoutes * @type {State} */ const state = new State('_a', {}); - - const specified = !!state.index; - const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; - const id = exists ? state.index : config.get('defaultIndex'); + const id = getIndexPatternId(state.index, indexPatternList, uiSettings.get('defaultIndex')); state.destroy(); - return Promise.props({ - list: savedObjects, + list: indexPatternList, loaded: indexPatterns.get(id), stateVal: state.index, - stateValFound: specified && exists + stateValFound: !!state.index && id === state.index, }); }), - savedSearch: savedSearches.get(savedSearchId) + savedSearch: getServices().getSavedSearchById(savedSearchId, kbnUrl) .then((savedSearch) => { if (savedSearchId) { chrome.recentlyAccessed.add( @@ -168,6 +164,7 @@ uiRoutes }, } }); +}); app.directive('discoverApp', function () { return { @@ -189,12 +186,12 @@ function discoverController( config, kbnUrl, localStorage, - uiCapabilities + uiCapabilities, + getAppState, + globalState, ) { const responseHandler = vislibSeriesResponseHandlerProvider().handler; - const getUnhashableStates = Private(getUnhashableStatesProvider); - - const queryFilter = Private(FilterBarQueryFilterProvider); + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); const inspectorAdapters = { requests: new RequestAdapter() @@ -234,6 +231,7 @@ function discoverController( if (abortController) abortController.abort(); savedSearch.destroy(); subscriptions.unsubscribe(); + filterStateManager.destroy(); }); const $appStatus = $scope.appStatus = this.appStatus = { @@ -329,7 +327,7 @@ function discoverController( anchorElement, allowEmbed: false, allowShortUrl: uiCapabilities.discover.createShortUrl, - shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), + shareableUrl: unhashUrl(window.location.href), objectId: savedSearch.id, objectType: 'search', sharingData: { @@ -392,7 +390,7 @@ function discoverController( $scope.searchSource.setParent(timeRangeSearchSource); const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; - docTitle.change(`Discover${pageTitleSuffix}`); + chrome.docTitle.change(`Discover${pageTitleSuffix}`); const discoverBreadcrumbsTitle = i18n.translate('kbn.discover.discoverBreadcrumbTitle', { defaultMessage: 'Discover', }); @@ -412,12 +410,12 @@ function discoverController( const $state = $scope.state = new AppState(getStateDefaults()); - $scope.filters = queryFilter.getFilters(); + $scope.filters = filterManager.getFilters(); $scope.screenTitle = savedSearch.title; $scope.onFiltersUpdated = filters => { - // The filters will automatically be set when the queryFilter emits an update event (see below) - queryFilter.setFilters(filters); + // The filters will automatically be set when the filterManager emits an update event (see below) + filterManager.setFilters(filters); }; const getFieldCounts = async () => { @@ -578,9 +576,9 @@ function discoverController( }); // update data source when filters update - subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { + subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { - $scope.filters = queryFilter.getFilters(); + $scope.filters = filterManager.getFilters(); $scope.updateDataSource().then(function () { $state.save(); }); @@ -588,7 +586,7 @@ function discoverController( })); // fetch data when filters fire fetch event - subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { + subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: $scope.fetch })); @@ -812,7 +810,7 @@ function discoverController( function logInspectorRequest() { inspectorAdapters.requests.reset(); const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { - defaultMessage: 'Data', + defaultMessage: 'data', }); const description = i18n.translate('kbn.discover.inspectorRequestDescription', { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', @@ -874,7 +872,7 @@ function discoverController( .setField('size', $scope.opts.sampleSize) .setField('sort', getSortForSearchSource($state.sort, indexPattern)) .setField('query', !$state.query ? null : $state.query) - .setField('filter', queryFilter.getFilters()); + .setField('filter', filterManager.getFilters()); }); $scope.setSortOrder = function setSortOrder(sortPair) { @@ -884,8 +882,8 @@ function discoverController( // TODO: On array fields, negating does not negate the combination, rather all terms $scope.filterQuery = function (field, values, operation) { $scope.indexPattern.popularizeField(field, 1); - const newFilters = generateFilters(queryFilter, field, values, operation, $scope.indexPattern.id); - return queryFilter.addFilters(newFilters); + const newFilters = generateFilters(filterManager, field, values, operation, $scope.indexPattern.id); + return filterManager.addFilters(newFilters); }; $scope.addColumn = function addColumn(columnName) { @@ -932,7 +930,7 @@ function discoverController( query: '', language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }; - queryFilter.removeAll(); + filterManager.removeAll(); $state.save(); $scope.fetch(); }; @@ -940,8 +938,7 @@ function discoverController( const updateStateFromSavedQuery = (savedQuery) => { $state.query = savedQuery.attributes.query; $state.save(); - - queryFilter.setFilters(savedQuery.attributes.filters || []); + filterManager.setFilters(savedQuery.attributes.filters || []); if (savedQuery.attributes.timefilter) { timefilter.setTime({ @@ -971,9 +968,8 @@ function discoverController( $scope.savedQuery = undefined; return; } - if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { - savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { + getSavedQuery(newSavedQueryId).then((savedQuery) => { $scope.$evalAsync(() => { $scope.savedQuery = savedQuery; updateStateFromSavedQuery(savedQuery); @@ -982,6 +978,7 @@ function discoverController( } }); + async function setupVisualization() { // If no timefield has been specified we don't create a histogram of messages if (!$scope.opts.timefield) return; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts index e6c890c9a66a22..af9556656afab9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts @@ -16,13 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import { getServices, IndexPatterns } from '../kibana_services'; +import { getAngularModule, wrapInI18nContext, getServices } from '../kibana_services'; // @ts-ignore -import { getRootBreadcrumbs } from '../breadcrumbs'; +import { getRootBreadcrumbs } from '../helpers/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) { +import { Doc } from '../components/doc/doc'; + +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +const { timefilter } = getServices(); +const app = getAngularModule(); +app.directive('discoverDoc', function(reactDirective: any) { return reactDirective( wrapInI18nContext(Doc), [ @@ -36,28 +42,28 @@ uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: ); }); -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}`, +app.config(($routeProvider: any) => { + $routeProvider + .when('/discover/doc/:indexPattern/:index/:type', { + redirectTo: '/discover/doc/:indexPattern/:index', + }) + // the new route, es 7 deprecated types, es 8 removed them + .when('/discover/doc/:indexPattern/:index', { + controller: ($scope: LazyScope, $route: any, es: any) => { + 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 = getServices().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/angular/doc_table/__tests__/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js index 417d521dd44eda..2c6718e44894fc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; import _ from 'lodash'; import ngMock from 'ng_mock'; import 'ui/private'; -import '..'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import hits from 'fixtures/real_hits'; @@ -65,8 +65,8 @@ const destroy = function () { describe('docTable', function () { let $elem; - - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach(function () { $elem = angular.element(` pluginInstance.initializeServices(true)); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach( ngMock.inject(function (_config_, $rootScope, Private) { config = _config_; 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 deleted file mode 100644 index 7462de544dbce3..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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 { 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/angular/doc_table/components/pager/index.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts new file mode 100644 index 00000000000000..3a037971a1253d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/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 { wrapInI18nContext } from '../../../../kibana_services'; +import { ToolBarPagerText } from './tool_bar_pager_text'; +import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; + +export function createToolBarPagerTextDirective(reactDirective: any) { + return reactDirective(wrapInI18nContext(ToolBarPagerText)); +} + +export function createToolBarPagerButtonsDirective(reactDirective: any) { + return reactDirective(wrapInI18nContext(ToolBarPagerButtons)); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts index f447c545077293..055f14f164476f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts @@ -17,11 +17,10 @@ * under the License. */ import { wrapInI18nContext } from 'ui/i18n'; -import { getServices } from '../../../kibana_services'; +import { IUiSettingsClient } from 'kibana/public'; import { TableHeader } from './table_header/table_header'; -const module = getServices().uiModules.get('app/discover'); -module.directive('kbnTableHeader', function(reactDirective: any, config: any) { +export function createTableHeaderDirective(reactDirective: any, config: IUiSettingsClient) { return reactDirective( wrapInI18nContext(TableHeader), [ @@ -40,4 +39,4 @@ module.directive('kbnTableHeader', function(reactDirective: any, config: any) { isShortDots: config.get('shortDots:enable'), } ); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/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 index 09ba77c7c49992..e5706b5e3c9bb9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/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 '../../../../kibana_services'; +import { IndexPattern, IFieldType } from '../../../../kibana_services'; function getMockIndexPattern() { return ({ @@ -40,7 +40,7 @@ function getMockIndexPattern() { aggregatable: false, searchable: true, sortable: true, - } as FieldType; + } as IFieldType; } else { return { name, @@ -48,7 +48,7 @@ function getMockIndexPattern() { aggregatable: false, searchable: true, sortable: false, - } as FieldType; + } as IFieldType; } }, } as unknown) as IndexPattern; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.js deleted file mode 100644 index 6f5a94442e9774..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.js +++ /dev/null @@ -1,219 +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 rison from 'rison-node'; -import '../../doc_viewer'; -import { noWhiteSpace } from '../../../../../common/utils/no_white_space'; - -import openRowHtml from './table_row/open.html'; -import detailsHtml from './table_row/details.html'; -import { getServices } from '../../../kibana_services'; -import { dispatchRenderComplete } from '../../../../../../../../plugins/kibana_utils/public'; -import cellTemplateHtml from '../components/table_row/cell.html'; -import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; -import { esFilters } from '../../../../../../../../plugins/data/public'; - -const module = getServices().uiModules.get('app/discover'); - -// guesstimate at the minimum number of chars wide cells in the table should be -const MIN_LINE_LENGTH = 20; - -/** - * kbnTableRow directive - * - * Display a row in the table - * ``` - * - * ``` - */ -module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl, config) { - const cellTemplate = _.template(noWhiteSpace(cellTemplateHtml)); - const truncateByHeightTemplate = _.template(noWhiteSpace(truncateByHeightTemplateHtml)); - - return { - restrict: 'A', - scope: { - columns: '=', - filter: '=', - filters: '=?', - indexPattern: '=', - row: '=kbnTableRow', - onAddColumn: '=?', - onRemoveColumn: '=?', - }, - link: function ($scope, $el) { - $el.after(''); - $el.empty(); - - // when we compile the details, we use this $scope - let $detailsScope; - - // when we compile the toggle button in the summary, we use this $scope - let $toggleScope; - - // toggle display of the rows details, a full list of the fields from each row - $scope.toggleRow = function () { - const $detailsTr = $el.next(); - - $scope.open = !$scope.open; - - /// - // add/remove $details children - /// - - $detailsTr.toggle($scope.open); - - if (!$scope.open) { - // close the child scope if it exists - $detailsScope.$destroy(); - // no need to go any further - return; - } else { - $detailsScope = $scope.$new(); - } - - // empty the details and rebuild it - $detailsTr.html(detailsHtml); - $detailsScope.row = $scope.row; - $detailsScope.hit = $scope.row; - $detailsScope.uriEncodedId = encodeURIComponent($detailsScope.hit._id); - - $compile($detailsTr)($detailsScope); - }; - - $scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight', '[]columns'], function () { - createSummaryRow($scope.row, $scope.row._id); - }); - - $scope.inlineFilter = function inlineFilter($event, type) { - const column = $($event.target).data().column; - const field = $scope.indexPattern.fields.getByName(column); - $scope.filter(field, $scope.flattenedRow[column], type); - }; - - $scope.getContextAppHref = () => { - const path = kbnUrl.eval('#/context/{{ indexPattern }}/{{ anchorId }}', { - anchorId: $scope.row._id, - indexPattern: $scope.indexPattern.id, - }); - const hash = $httpParamSerializer({ - _a: rison.encode({ - columns: $scope.columns, - filters: ($scope.filters || []).map(esFilters.disableFilter), - }), - }); - return `${path}?${hash}`; - }; - - // create a tr element that lists the value for each *column* - function createSummaryRow(row) { - const indexPattern = $scope.indexPattern; - $scope.flattenedRow = indexPattern.flattenHit(row); - - // We just create a string here because its faster. - const newHtmls = [openRowHtml]; - - const mapping = indexPattern.fields.getByName; - const hideTimeColumn = config.get('doc_table:hideTimeColumn'); - if (indexPattern.timeFieldName && !hideTimeColumn) { - newHtmls.push( - cellTemplate({ - timefield: true, - formatted: _displayField(row, indexPattern.timeFieldName), - filterable: - mapping(indexPattern.timeFieldName).filterable && _.isFunction($scope.filter), - column: indexPattern.timeFieldName, - }) - ); - } - - $scope.columns.forEach(function (column) { - const isFilterable = - $scope.flattenedRow[column] !== undefined && - mapping(column) && - mapping(column).filterable && - _.isFunction($scope.filter); - - newHtmls.push( - cellTemplate({ - timefield: false, - sourcefield: column === '_source', - formatted: _displayField(row, column, true), - filterable: isFilterable, - column, - }) - ); - }); - - let $cells = $el.children(); - newHtmls.forEach(function (html, i) { - const $cell = $cells.eq(i); - if ($cell.data('discover:html') === html) return; - - const reuse = _.find($cells.slice(i + 1), function (cell) { - return $.data(cell, 'discover:html') === html; - }); - - const $target = reuse ? $(reuse).detach() : $(html); - $target.data('discover:html', html); - const $before = $cells.eq(i - 1); - if ($before.length) { - $before.after($target); - } else { - $el.append($target); - } - - // rebuild cells since we modified the children - $cells = $el.children(); - - if (!reuse) { - $toggleScope = $scope.$new(); - $compile($target)($toggleScope); - } - }); - - if ($scope.open) { - $detailsScope.row = row; - } - - // trim off cells that were not used rest of the cells - $cells.filter(':gt(' + (newHtmls.length - 1) + ')').remove(); - dispatchRenderComplete($el[0]); - } - - /** - * Fill an element with the value of a field - */ - function _displayField(row, fieldName, truncate) { - const indexPattern = $scope.indexPattern; - const text = indexPattern.formatField(row, fieldName); - - if (truncate && text.length > MIN_LINE_LENGTH) { - return truncateByHeightTemplate({ - body: text, - }); - } - - return text; - } - }, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts new file mode 100644 index 00000000000000..8ff4ab46ef5321 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts @@ -0,0 +1,221 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 { IUiSettingsClient } from 'kibana/public'; +// @ts-ignore +import rison from 'rison-node'; +import '../../doc_viewer'; +// @ts-ignore +import { noWhiteSpace } from '../../../../../common/utils/no_white_space'; + +import openRowHtml from './table_row/open.html'; +import detailsHtml from './table_row/details.html'; + +import { dispatchRenderComplete } from '../../../../../../../../plugins/kibana_utils/public'; +import cellTemplateHtml from '../components/table_row/cell.html'; +import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; +import { esFilters } from '../../../../../../../../plugins/data/public'; + +// guesstimate at the minimum number of chars wide cells in the table should be +const MIN_LINE_LENGTH = 20; + +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +export function createTableRowDirective( + $compile: ng.ICompileService, + $httpParamSerializer: any, + kbnUrl: any, + config: IUiSettingsClient +) { + const cellTemplate = _.template(noWhiteSpace(cellTemplateHtml)); + const truncateByHeightTemplate = _.template(noWhiteSpace(truncateByHeightTemplateHtml)); + + return { + restrict: 'A', + scope: { + columns: '=', + filter: '=', + filters: '=?', + indexPattern: '=', + row: '=kbnTableRow', + onAddColumn: '=?', + onRemoveColumn: '=?', + }, + link: ($scope: LazyScope, $el: JQuery) => { + $el.after(''); + $el.empty(); + + // when we compile the details, we use this $scope + let $detailsScope: LazyScope; + + // when we compile the toggle button in the summary, we use this $scope + let $toggleScope; + + // toggle display of the rows details, a full list of the fields from each row + $scope.toggleRow = () => { + const $detailsTr = $el.next(); + + $scope.open = !$scope.open; + + /// + // add/remove $details children + /// + + $detailsTr.toggle($scope.open); + + if (!$scope.open) { + // close the child scope if it exists + $detailsScope.$destroy(); + // no need to go any further + return; + } else { + $detailsScope = $scope.$new(); + } + + // empty the details and rebuild it + $detailsTr.html(detailsHtml); + $detailsScope.row = $scope.row; + $detailsScope.hit = $scope.row; + $detailsScope.uriEncodedId = encodeURIComponent($detailsScope.hit._id); + + $compile($detailsTr)($detailsScope); + }; + + $scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight', '[]columns'], () => { + createSummaryRow($scope.row); + }); + + $scope.inlineFilter = function inlineFilter($event: any, type: string) { + const column = $($event.target).data().column; + const field = $scope.indexPattern.fields.getByName(column); + $scope.filter(field, $scope.flattenedRow[column], type); + }; + + $scope.getContextAppHref = () => { + const path = kbnUrl.eval('#/discover/context/{{ indexPattern }}/{{ anchorId }}', { + anchorId: $scope.row._id, + indexPattern: $scope.indexPattern.id, + }); + const hash = $httpParamSerializer({ + _a: rison.encode({ + columns: $scope.columns, + filters: ($scope.filters || []).map(esFilters.disableFilter), + }), + }); + return `${path}?${hash}`; + }; + + // create a tr element that lists the value for each *column* + function createSummaryRow(row: any) { + const indexPattern = $scope.indexPattern; + $scope.flattenedRow = indexPattern.flattenHit(row); + + // We just create a string here because its faster. + const newHtmls = [openRowHtml]; + + const mapping = indexPattern.fields.getByName; + const hideTimeColumn = config.get('doc_table:hideTimeColumn'); + if (indexPattern.timeFieldName && !hideTimeColumn) { + newHtmls.push( + cellTemplate({ + timefield: true, + formatted: _displayField(row, indexPattern.timeFieldName), + filterable: + mapping(indexPattern.timeFieldName).filterable && _.isFunction($scope.filter), + column: indexPattern.timeFieldName, + }) + ); + } + + $scope.columns.forEach(function(column: any) { + const isFilterable = + $scope.flattenedRow[column] !== undefined && + mapping(column) && + mapping(column).filterable && + _.isFunction($scope.filter); + + newHtmls.push( + cellTemplate({ + timefield: false, + sourcefield: column === '_source', + formatted: _displayField(row, column, true), + filterable: isFilterable, + column, + }) + ); + }); + + let $cells = $el.children(); + newHtmls.forEach(function(html, i) { + const $cell = $cells.eq(i); + if ($cell.data('discover:html') === html) return; + + const reuse = _.find($cells.slice(i + 1), function(cell: any) { + return $.data(cell, 'discover:html') === html; + }); + + const $target = reuse ? $(reuse).detach() : $(html); + $target.data('discover:html', html); + const $before = $cells.eq(i - 1); + if ($before.length) { + $before.after($target); + } else { + $el.append($target); + } + + // rebuild cells since we modified the children + $cells = $el.children(); + + if (!reuse) { + $toggleScope = $scope.$new(); + $compile($target)($toggleScope); + } + }); + + if ($scope.open) { + $detailsScope.row = row; + } + + // trim off cells that were not used rest of the cells + $cells.filter(':gt(' + (newHtmls.length - 1) + ')').remove(); + dispatchRenderComplete($el[0]); + } + + /** + * Fill an element with the value of a field + */ + function _displayField(row: any, fieldName: string, truncate = false) { + const indexPattern = $scope.indexPattern; + const text = indexPattern.formatField(row, fieldName); + + if (truncate && text.length > MIN_LINE_LENGTH) { + return truncateByHeightTemplate({ + body: text, + }); + } + + return text; + } + }, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html index 5c8785e8dc5f9f..d149a9023816a8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html @@ -30,7 +30,7 @@ @@ -48,5 +48,5 @@ on-remove-column="onRemoveColumn" >
- + diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.js deleted file mode 100644 index 72943671fec22d..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.js +++ /dev/null @@ -1,126 +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 html from './doc_table.html'; -import './infinite_scroll'; -import './components/table_header'; -import './components/table_row'; -import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; -import { getServices } from '../../kibana_services'; -import './components/pager'; -import './lib/pager'; - -import { getLimitedSearchResultsMessage } from './doc_table_strings'; - -const { uiModules } = getServices(); - -uiModules.get('app/discover') - .directive('docTable', function (config, getAppState, pagerFactory, $filter) { - return { - restrict: 'E', - template: html, - scope: { - sorting: '=', - columns: '=', - hits: '=', - totalHitCount: '=', - indexPattern: '=', - isLoading: '=?', - infiniteScroll: '=?', - filter: '=?', - filters: '=?', - minimumVisibleRows: '=?', - onAddColumn: '=?', - onChangeSortOrder: '=?', - onMoveColumn: '=?', - onRemoveColumn: '=?', - inspectorAdapters: '=?', - }, - link: function ($scope, $el) { - $scope.$watch('minimumVisibleRows', (minimumVisibleRows) => { - $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50); - }); - - $scope.persist = { - sorting: $scope.sorting, - columns: $scope.columns - }; - - const limitTo = $filter('limitTo'); - const calculateItemsOnPage = () => { - $scope.pager.setTotalItems($scope.hits.length); - $scope.pageOfItems = limitTo($scope.hits, $scope.pager.pageSize, $scope.pager.startIndex); - }; - - $scope.limitedResultsWarning = getLimitedSearchResultsMessage(config.get('discover:sampleSize')); - - $scope.addRows = function () { - $scope.limit += 50; - }; - - // This exists to fix the problem of an empty initial column list not playing nice with watchCollection. - $scope.$watch('columns', function (columns) { - if (columns.length !== 0) return; - - const $state = getAppState(); - $scope.columns.push('_source'); - if ($state) $state.replace(); - }); - - $scope.$watchCollection('columns', function (columns, oldColumns) { - if (oldColumns.length === 1 && oldColumns[0] === '_source' && $scope.columns.length > 1) { - _.pull($scope.columns, '_source'); - } - - if ($scope.columns.length === 0) $scope.columns.push('_source'); - }); - - $scope.$watch('hits', hits => { - if (!hits) return; - - // Reset infinite scroll limit - $scope.limit = 50; - - if (hits.length === 0) { - dispatchRenderComplete($el[0]); - } - - if ($scope.infiniteScroll) return; - $scope.pager = pagerFactory.create(hits.length, 50, 1); - calculateItemsOnPage(); - }); - - $scope.pageOfItems = []; - $scope.onPageNext = () => { - $scope.pager.nextPage(); - calculateItemsOnPage(); - }; - - $scope.onPagePrevious = () => { - $scope.pager.previousPage(); - calculateItemsOnPage(); - }; - - $scope.shouldShowLimitedResultsWarning = () => ( - !$scope.pager.hasNextPage && $scope.pager.totalItems < $scope.totalHitCount - ); - } - }; - }); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts new file mode 100644 index 00000000000000..92ebc24c6e3782 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts @@ -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 _ from 'lodash'; +import { IUiSettingsClient } from 'kibana/public'; +import html from './doc_table.html'; +import './infinite_scroll'; +import './components/table_header'; +import './components/table_row'; +import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; +import './components/pager'; +import './lib/pager'; +// @ts-ignore +import { getLimitedSearchResultsMessage } from './doc_table_strings'; + +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +export function createDocTableDirective( + config: IUiSettingsClient, + getAppState: any, + pagerFactory: any, + $filter: any +) { + return { + restrict: 'E', + template: html, + scope: { + sorting: '=', + columns: '=', + hits: '=', + totalHitCount: '=', + indexPattern: '=', + isLoading: '=?', + infiniteScroll: '=?', + filter: '=?', + filters: '=?', + minimumVisibleRows: '=?', + onAddColumn: '=?', + onChangeSortOrder: '=?', + onMoveColumn: '=?', + onRemoveColumn: '=?', + inspectorAdapters: '=?', + }, + link: ($scope: LazyScope, $el: JQuery) => { + $scope.$watch('minimumVisibleRows', (minimumVisibleRows: number) => { + $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50); + }); + + $scope.persist = { + sorting: $scope.sorting, + columns: $scope.columns, + }; + + const limitTo = $filter('limitTo'); + const calculateItemsOnPage = () => { + $scope.pager.setTotalItems($scope.hits.length); + $scope.pageOfItems = limitTo($scope.hits, $scope.pager.pageSize, $scope.pager.startIndex); + }; + + $scope.limitedResultsWarning = getLimitedSearchResultsMessage( + config.get('discover:sampleSize') + ); + + $scope.addRows = function() { + $scope.limit += 50; + }; + + // This exists to fix the problem of an empty initial column list not playing nice with watchCollection. + $scope.$watch('columns', function(columns: string[]) { + if (columns.length !== 0) return; + + const $state = getAppState(); + $scope.columns.push('_source'); + if ($state) $state.replace(); + }); + + $scope.$watchCollection('columns', function(columns: string[], oldColumns: string[]) { + if (oldColumns.length === 1 && oldColumns[0] === '_source' && $scope.columns.length > 1) { + _.pull($scope.columns, '_source'); + } + + if ($scope.columns.length === 0) $scope.columns.push('_source'); + }); + + $scope.$watch('hits', (hits: any) => { + if (!hits) return; + + // Reset infinite scroll limit + $scope.limit = 50; + + if (hits.length === 0) { + dispatchRenderComplete($el[0]); + } + + if ($scope.infiniteScroll) return; + $scope.pager = pagerFactory.create(hits.length, 50, 1); + calculateItemsOnPage(); + }); + + $scope.pageOfItems = []; + $scope.onPageNext = () => { + $scope.pager.nextPage(); + calculateItemsOnPage(); + }; + + $scope.onPagePrevious = () => { + $scope.pager.previousPage(); + calculateItemsOnPage(); + }; + + $scope.shouldShowLimitedResultsWarning = () => + !$scope.pager.hasNextPage && $scope.pager.totalItems < $scope.totalHitCount; + }, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.js deleted file mode 100644 index bf12deeb6b05fa..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.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 $ from 'jquery'; -import { getServices } from '../../kibana_services'; - -const module = getServices().uiModules.get('app/discover'); - -module.directive('kbnInfiniteScroll', function () { - return { - restrict: 'E', - scope: { - more: '=' - }, - link: function ($scope, $element) { - const $window = $(window); - let checkTimer; - - function onScroll() { - if (!$scope.more) return; - - const winHeight = $window.height(); - const winBottom = winHeight + $window.scrollTop(); - const elTop = $element.offset().top; - const remaining = elTop - winBottom; - - if (remaining <= winHeight * 0.50) { - $scope[$scope.$$phase ? '$eval' : '$apply'](function () { - $scope.more(); - }); - } - } - - function scheduleCheck() { - if (checkTimer) return; - checkTimer = setTimeout(function () { - checkTimer = null; - onScroll(); - }, 50); - } - - $window.on('scroll', scheduleCheck); - $scope.$on('$destroy', function () { - clearTimeout(checkTimer); - $window.off('scroll', scheduleCheck); - }); - scheduleCheck(); - } - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts new file mode 100644 index 00000000000000..1a8ad372bbb8a2 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.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 $ from 'jquery'; + +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +export function createInfiniteScrollDirective() { + return { + restrict: 'E', + scope: { + more: '=', + }, + link: ($scope: LazyScope, $element: JQuery) => { + const $window = $(window); + let checkTimer: any; + + function onScroll() { + if (!$scope.more) return; + + const winHeight = Number($window.height()); + const winBottom = Number(winHeight) + Number($window.scrollTop()); + const offset = $element.offset(); + const elTop = offset ? offset.top : 0; + const remaining = elTop - winBottom; + + if (remaining <= winHeight * 0.5) { + $scope[$scope.$$phase ? '$eval' : '$apply'](function() { + $scope.more(); + }); + } + } + + function scheduleCheck() { + if (checkTimer) return; + checkTimer = setTimeout(function() { + checkTimer = null; + onScroll(); + }, 50); + } + + $window.on('scroll', scheduleCheck); + $scope.$on('$destroy', function() { + clearTimeout(checkTimer); + $window.off('scroll', scheduleCheck); + }); + scheduleCheck(); + }, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts index bb32da0d11fc0f..ebf715a64d9397 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts @@ -17,11 +17,11 @@ * under the License. */ -import { StaticIndexPattern } from '../../../kibana_services'; +import { IIndexPattern } from '../../../../../../../../plugins/data/public'; import { SortOrder } from '../components/table_header/helpers'; export function getSort( sort?: SortOrder[], - indexPattern?: StaticIndexPattern, + indexPattern?: IIndexPattern, defaultSortOrder?: SortOrder ): any; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.js deleted file mode 100644 index 5d488fab0c87ff..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.js +++ /dev/null @@ -1,31 +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 { getServices } from '../../../../kibana_services'; -import { Pager } from './pager'; - -const app = getServices().uiModules.get('kibana'); - -app.factory('pagerFactory', () => { - return { - create(...args) { - return new Pager(...args); - } - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts new file mode 100644 index 00000000000000..fe576b63568dd6 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.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. + */ +// @ts-ignore +import { Pager } from './pager'; + +export function createPagerFactory() { + return { + create(...args: unknown[]) { + return new Pager(...args); + }, + }; +} 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 index c13c3545284139..6ba47b839563bc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts @@ -17,13 +17,9 @@ * under the License. */ -// @ts-ignore -import { getServices } from '../kibana_services'; -import { DocViewer } from '../doc_viewer/doc_viewer'; +import { DocViewer } from '../components/doc_viewer/doc_viewer'; -const { uiModules } = getServices(); - -uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { +export function createDocViewerDirective(reactDirective: any) { return reactDirective( DocViewer, [ @@ -46,4 +42,4 @@ uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { }, } ); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/application.ts b/src/legacy/core_plugins/kibana/public/discover/application.ts new file mode 100644 index 00000000000000..83f4a5962e3cdf --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/application.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular from 'angular'; + +/** + * Here's where Discover's inner angular is mounted and rendered + */ +export async function renderApp(moduleName: string, element: HTMLElement) { + await import('./angular'); + const $injector = mountDiscoverApp(moduleName, element); + return () => $injector.get('$rootScope').$destroy(); +} + +function mountDiscoverApp(moduleName: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + const appWrapper = document.createElement('div'); + appWrapper.setAttribute('ng-view', ''); + mountpoint.appendChild(appWrapper); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + $injector.get('globalState'); + element.appendChild(mountpoint); + return $injector; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx index b3efd23ea48d03..4df56483fa5c60 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx @@ -28,7 +28,7 @@ jest.mock('../doc_viewer/doc_viewer', () => ({ DocViewer: 'test', })); -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { return { getServices: () => ({ metadata: { @@ -45,16 +45,6 @@ beforeEach(() => { jest.clearAllMocks(); }); -// Suppress warnings about "act" until we use React 16.9 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - export const waitForPromises = () => new Promise(resolve => setTimeout(resolve, 0)); /** diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx index 0e0e6ed110ca67..7020addb2bc6d0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx @@ -19,9 +19,10 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; +import { IndexPatternsContract } from 'src/plugins/data/public'; import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; -import { IndexPatterns, ElasticSearchHit, getServices } from '../kibana_services'; +import { ElasticSearchHit, getServices } from '../../kibana_services'; export interface ElasticSearchResult { hits: { @@ -50,7 +51,7 @@ export interface DocProps { /** * IndexPatternService to get a given index pattern by ID */ - indexPatternService: IndexPatterns; + indexPatternService: IndexPatternsContract; /** * Client of ElasticSearch to use for the query */ diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx similarity index 88% rename from src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx index 083a5997ac5dd9..2420eb2cd22bb7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx @@ -16,20 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search'; import { DocProps } from './doc'; -// Suppress warnings about "act" until we use React 16.9 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - describe('Test of helper / hook', () => { test('buildSearchBody', () => { const indexPattern = { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts rename to src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts index 538fbed821f00b..20bffe829de166 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts @@ -17,7 +17,7 @@ * under the License. */ import { useEffect, useState } from 'react'; -import { ElasticSearchHit, IndexPattern } from '../kibana_services'; +import { ElasticSearchHit, IndexPattern } from '../../kibana_services'; import { DocProps } from './doc'; export enum ElasticRequestState { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_doc_viewer.scss b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_doc_viewer.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/_doc_viewer.scss rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_doc_viewer.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx index 12473b25802f23..158ed4ccc7759e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx @@ -28,7 +28,7 @@ import { getDocViewsSorted as mockGetDocViewsSorted, } from 'ui/registry/doc_views'; -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { return { getServices: () => ({ docViewsRegistry: { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx similarity index 90% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx index aa737ebd8dcf18..a2d58439ad031c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx @@ -17,8 +17,9 @@ * under the License. */ import React from 'react'; +import { DocView } from 'ui/registry/doc_views_types'; import { EuiTabbedContent } from '@elastic/eui'; -import { getServices, DocViewRenderProps } from '../kibana_services'; +import { getServices, DocViewRenderProps } from '../../kibana_services'; import { DocViewerTab } from './doc_viewer_tab'; /** @@ -31,7 +32,7 @@ export function DocViewer(renderProps: DocViewRenderProps) { const { docViewsRegistry } = getServices(); const tabs = docViewsRegistry .getDocViewsSorted(renderProps.hit) - .map(({ title, render, component }, idx) => { + .map(({ title, render, component }: DocView, idx: number) => { return { id: title, name: title, diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx 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/components/doc_viewer/doc_viewer_render_tab.test.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx index 5fa2d24dfa04cf..476d7cef159fb0 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/components/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 '../kibana_services'; +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/components/doc_viewer/doc_viewer_render_tab.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx index 750ef6b6061e13..8ac11caefff905 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useRef, useEffect } from 'react'; -import { DocViewRenderFn, DocViewRenderProps } from '../kibana_services'; +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/components/doc_viewer/doc_viewer_tab.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx index 3721ba5818d412..19558129eae8d0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; -import { DocViewRenderProps, DocViewRenderFn } from '../kibana_services'; +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/components/fetch_error/fetch_error.js b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js deleted file mode 100644 index 612ca860f80317..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React, { Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; -import { getServices } from '../../kibana_services'; -const { uiModules, wrapInI18nContext, chrome } = getServices(); - -const DiscoverFetchError = ({ fetchError }) => { - if (!fetchError) { - return null; - } - - let body; - - if (fetchError.lang === 'painless') { - const managementUrl = chrome.navLinks.get('kibana:management').url; - const url = `${managementUrl}/kibana/index_patterns`; - - body = ( -

- - ), - managementLink: ( - - - - ), - }} - /> -

- ); - } - - return ( - - - - - - - {body} - - {fetchError.error} - - - - - - - ); -}; - -const app = uiModules.get('apps/discover', ['react']); - -app.directive('discoverFetchError', reactDirective => - reactDirective(wrapInI18nContext(DiscoverFetchError)) -); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx new file mode 100644 index 00000000000000..8f67c1952f9986 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx @@ -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 React, { Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; +import { getAngularModule, wrapInI18nContext, getServices } from '../../kibana_services'; + +interface Props { + fetchError: { + lang: string; + script: string; + message: string; + error: string; + }; +} + +const DiscoverFetchError = ({ fetchError }: Props) => { + if (!fetchError) { + return null; + } + + let body; + + if (fetchError.lang === 'painless') { + const { chrome } = getServices(); + const mangagementUrlObj = chrome.navLinks.get('kibana:management'); + const managementUrl = mangagementUrlObj ? mangagementUrlObj.url : ''; + const url = `${managementUrl}/kibana/index_patterns`; + + body = ( +

+ + ), + managementLink: ( + + + + ), + }} + /> +

+ ); + } + + return ( + + + + + + + {body} + + {fetchError.error} + + + + + + + ); +}; + +export function createFetchErrorDirective(reactDirective: any) { + return reactDirective(wrapInI18nContext(DiscoverFetchError)); +} + +getAngularModule().directive('discoverFetchError', createFetchErrorDirective); 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 cfcb6540771523..0c633e23c5e4b7 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 @@ -20,16 +20,15 @@ import $ from 'jquery'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../kibana_services'; import html from './discover_field.html'; import 'ui/directives/css_truncate'; import 'ui/directives/field_name'; import './string_progress_bar'; import detailsHtml from './lib/detail_views/string.html'; -const { uiModules, capabilities } = getServices(); -const app = uiModules.get('apps/discover'); -app.directive('discoverField', function ($compile) { + +export function createDiscoverFieldDirective($compile) { return { restrict: 'E', template: html, @@ -78,7 +77,7 @@ app.directive('discoverField', function ($compile) { }; - $scope.canVisualize = capabilities.visualize.show; + $scope.canVisualize = getServices().capabilities.visualize.show; $scope.toggleDisplay = function (field) { if (field.display) { @@ -135,4 +134,5 @@ app.directive('discoverField', function ($compile) { init(); } }; -}); +} + 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 b78f993e187725..69865ec4243253 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 @@ -16,18 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import { getServices } from '../../kibana_services'; +import { wrapInI18nContext } 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) { +export function createFieldSearchDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(DiscoverFieldSearch), [ ['onChange', { watchDepth: 'reference' }], ['value', { 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 5e3f678e388ad3..46c8fa854847aa 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 @@ -16,18 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import { getServices } from '../../kibana_services'; +import { wrapInI18nContext } 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) { +export function createIndexPatternSelectDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(DiscoverIndexPattern), [ ['indexPatternList', { watchDepth: 'reference' }], ['selectedIndexPattern', { watchDepth: 'reference' }], ['setIndexPattern', { watchDepth: 'reference' }], ]); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx index 23679c4db5a7f2..fb5c3c8d45ce85 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { EuiToolTip, EuiFlexItem, EuiFlexGroup, EuiTitle, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; - +import { i18n } from '@kbn/i18n'; export interface DiscoverIndexPatternTitleProps { /** * determines whether the change link is displayed @@ -65,18 +65,19 @@ export function DiscoverIndexPatternTitle({ } > onChange()} - > - ( - - ) - + iconSide="right" + iconType="arrowDown" + color="text" + /> )} 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 99a63efc0e0fca..8674726a81076d 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,25 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -//field_name directive will be replaced very soon -import 'ui/directives/field_name'; -import './discover_field'; -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 { - getServices, - FieldList -} from '../../kibana_services'; +import './discover_field'; +import './discover_field_search_directive'; +import './discover_index_pattern_directive'; +import { FieldList } from '../../../../../../../plugins/data/public'; import fieldChooserTemplate from './field_chooser.html'; -const { uiModules } = getServices(); -const app = uiModules.get('apps/discover'); - -app.directive('discFieldChooser', function ($location, config, $route) { +export function createFieldChooserDirective($location, config, $route) { return { restrict: 'E', scope: { @@ -50,12 +42,11 @@ app.directive('discFieldChooser', function ($location, config, $route) { }, template: fieldChooserTemplate, link: function ($scope) { - $scope.showFilter = false; - $scope.toggleShowFilter = () => $scope.showFilter = !$scope.showFilter; + $scope.toggleShowFilter = () => ($scope.showFilter = !$scope.showFilter); $scope.selectedIndexPattern = $scope.indexPatternList.find( - (pattern) => pattern.id === $scope.indexPattern.id + pattern => pattern.id === $scope.indexPattern.id ); $scope.indexPatternList = _.sortBy($scope.indexPatternList, o => o.get('title')); $scope.setIndexPattern = function (id) { @@ -68,23 +59,17 @@ app.directive('discFieldChooser', function ($location, config, $route) { $route.reload(); }); - const filter = $scope.filter = { - props: [ - 'type', - 'aggregatable', - 'searchable', - 'missing', - 'name' - ], + const filter = ($scope.filter = { + props: ['type', 'aggregatable', 'searchable', 'missing', 'name'], defaults: { missing: true, type: 'any', - name: '' + name: '', }, boolOpts: [ { label: 'any', value: undefined }, { label: 'yes', value: true }, - { label: 'no', value: false } + { label: 'no', value: false }, ], reset: function () { filter.vals = _.clone(filter.defaults); @@ -105,15 +90,18 @@ app.directive('discFieldChooser', function ($location, config, $route) { return _.some(filter.props, function (prop) { return filter.vals[prop] !== filter.defaults[prop]; }); - } - }; + }, + }); function isFieldFiltered(field) { - const matchFilter = (filter.vals.type === 'any' || field.type === filter.vals.type); - const isAggregatable = (filter.vals.aggregatable == null || field.aggregatable === filter.vals.aggregatable); - const isSearchable = (filter.vals.searchable == null || field.searchable === filter.vals.searchable); - const scriptedOrMissing = !filter.vals.missing || field.type === '_source' || field.scripted || field.rowCount > 0; - const matchName = (!filter.vals.name || field.name.indexOf(filter.vals.name) !== -1); + const matchFilter = filter.vals.type === 'any' || field.type === filter.vals.type; + const isAggregatable = + filter.vals.aggregatable == null || field.aggregatable === filter.vals.aggregatable; + const isSearchable = + filter.vals.searchable == null || field.searchable === filter.vals.searchable; + const scriptedOrMissing = + !filter.vals.missing || field.type === '_source' || field.scripted || field.rowCount > 0; + const matchName = !filter.vals.name || field.name.indexOf(filter.vals.name) !== -1; return matchFilter && isAggregatable && isSearchable && scriptedOrMissing && matchName; } @@ -131,7 +119,7 @@ app.directive('discFieldChooser', function ($location, config, $route) { filter.active = filter.getActive(); if (filter.vals) { let count = 0; - Object.keys(filter.vals).forEach((key) => { + Object.keys(filter.vals).forEach(key => { if (key === 'missing' || key === 'name') { return; } @@ -144,11 +132,7 @@ app.directive('discFieldChooser', function ($location, config, $route) { } }); - $scope.$watchMulti([ - '[]fieldCounts', - '[]columns', - '[]hits' - ], function (cur, prev) { + $scope.$watchMulti(['[]fieldCounts', '[]columns', '[]hits'], function (cur, prev) { const newHits = cur[2] !== prev[2]; let fields = $scope.fields; const columns = $scope.columns || []; @@ -198,7 +182,9 @@ app.directive('discFieldChooser', function ($location, config, $route) { }; function getVisualizeUrl(field) { - if (!$scope.state) {return '';} + if (!$scope.state) { + return ''; + } let agg = {}; const isGeoPoint = field.type === 'geo_point'; @@ -211,18 +197,17 @@ app.directive('discFieldChooser', function ($location, config, $route) { schema: 'segment', params: { field: field.name, - interval: 'auto' - } + interval: 'auto', + }, }; - } else if (isGeoPoint) { agg = { type: 'geohash_grid', schema: 'segment', params: { field: field.name, - precision: 3 - } + precision: 3, + }, }; } else { agg = { @@ -231,26 +216,28 @@ app.directive('discFieldChooser', function ($location, config, $route) { params: { field: field.name, size: parseInt(config.get('discover:aggs:terms:size'), 10), - orderBy: '2' - } + orderBy: '2', + }, }; } - return '#/visualize/create?' + $.param(_.assign(_.clone($location.search()), { - indexPattern: $scope.state.index, - type: type, - _a: rison.encode({ - filters: $scope.state.filters || [], - query: $scope.state.query || undefined, - vis: { + return ( + '#/visualize/create?' + + $.param( + _.assign(_.clone($location.search()), { + indexPattern: $scope.state.index, type: type, - aggs: [ - { schema: 'metric', type: 'count', 'id': '2' }, - agg, - ] - } - }) - })); + _a: rison.encode({ + filters: $scope.state.filters || [], + query: $scope.state.query || undefined, + vis: { + type: type, + aggs: [{ schema: 'metric', type: 'count', id: '2' }, agg], + }, + }), + }) + ) + ); } $scope.computeDetails = function (field, recompute) { @@ -261,7 +248,7 @@ app.directive('discFieldChooser', function ($location, config, $route) { hits: $scope.hits, field: field, count: 5, - grouped: false + grouped: false, }), }; _.each(field.details.buckets, function (bucket) { @@ -285,13 +272,14 @@ app.directive('discFieldChooser', function ($location, config, $route) { const fieldNamesInDocs = _.keys(fieldCounts); const fieldNamesInIndexPattern = _.map(indexPattern.fields, 'name'); - _.difference(fieldNamesInDocs, fieldNamesInIndexPattern) - .forEach(function (unknownFieldName) { - fieldSpecs.push({ - name: unknownFieldName, - type: 'unknown' - }); + _.difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach(function ( + unknownFieldName + ) { + fieldSpecs.push({ + name: unknownFieldName, + type: 'unknown', }); + }); const fields = new FieldList(indexPattern, fieldSpecs); @@ -303,6 +291,6 @@ app.directive('discFieldChooser', function ($location, config, $route) { return fields; } - } + }, }; -}); +} 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 deleted file mode 100644 index ca3a47cad50757..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.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 React from 'react'; -import { getServices } from '../../kibana_services'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiText, - EuiToolTip, -} from '@elastic/eui'; - -const { wrapInI18nContext, uiModules } = getServices(); -const module = uiModules.get('discover/field_chooser'); - -function StringFieldProgressBar(props) { - return ( - - - - - - - - {props.percent}% - - - - - ); -} - -module.directive('stringFieldProgressBar', function (reactDirective) { - return reactDirective(wrapInI18nContext(StringFieldProgressBar)); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx new file mode 100644 index 00000000000000..7e4fc79839a52d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx @@ -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 React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText, EuiToolTip } from '@elastic/eui'; +import { wrapInI18nContext } from '../../kibana_services'; + +interface Props { + percent: number; + count: number; +} + +function StringFieldProgressBar(props: Props) { + return ( + + + + + + + {props.percent}% + + + + ); +} + +export function createStringFieldProgressBarDirective(reactDirective: any) { + return reactDirective(wrapInI18nContext(StringFieldProgressBar)); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap index cc53e4bdcdcf93..2878b11040cf34 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -26,7 +26,7 @@ exports[`render 1`] = ` - diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js similarity index 90% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js index 0c3b52fbf0640c..ec1763f44f25f6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js @@ -32,11 +32,16 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { SavedObjectFinderUi } from '../../../../../../../plugins/kibana_react/public'; +import { getServices } from '../../kibana_services'; const SEARCH_OBJECT_TYPE = 'search'; export function OpenSearchPanel(props) { + const { + core: { uiSettings, savedObjects }, + } = getServices(); + return ( @@ -50,7 +55,7 @@ export function OpenSearchPanel(props) { - 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/components/top_nav/open_search_panel.test.js similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js index 3531088e3847c9..0c82aeea952943 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js @@ -20,10 +20,10 @@ import React from 'react'; import { shallow } from 'enzyme'; -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { return { getServices: () => ({ - SavedObjectFinder: jest.fn() + core: { uiSettings: {}, savedObjects: {} }, }), }; }); diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/README.md b/src/legacy/core_plugins/kibana/public/discover/context/README.md deleted file mode 100644 index 18ba118b4da798..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/context/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# 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/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts index beeb6a7338f9d2..3138008f3e3a00 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts @@ -20,4 +20,3 @@ 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 9fee0cfc3ea00a..273c7d80f216c1 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 @@ -21,7 +21,6 @@ import * as Rx from 'rxjs'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import { npStart } from 'ui/new_platform'; import { SearchSourceContract } from '../../../../../ui/public/courier'; import { esFilters, @@ -55,8 +54,6 @@ import { } from '../kibana_services'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; -const { data } = npStart.plugins; - interface SearchScope extends ng.IScope { columns?: string[]; description?: string; @@ -81,7 +78,7 @@ interface SearchEmbeddableConfig { editUrl: string; indexPatterns?: IndexPattern[]; editable: boolean; - queryFilter: unknown; + filterManager: FilterManager; } export class SearchEmbeddable extends Embeddable @@ -112,7 +109,7 @@ export class SearchEmbeddable extends Embeddable editUrl, indexPatterns, editable, - queryFilter, + filterManager, }: SearchEmbeddableConfig, initialInput: SearchInput, private readonly executeTriggerActions: TExecuteTriggerActions, @@ -124,7 +121,7 @@ export class SearchEmbeddable extends Embeddable parent ); - this.filterManager = queryFilter as FilterManager; + this.filterManager = filterManager; this.savedSearch = savedSearch; this.$rootScope = $rootScope; this.$compile = $compile; @@ -132,9 +129,10 @@ export class SearchEmbeddable extends Embeddable requests: new RequestAdapter(), }; this.initializeSearchScope(); - const { timefilter } = data.query.timefilter; - this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.fetch); + this.autoRefreshFetchSubscription = getServices() + .timefilter.getAutoRefreshFetch$() + .subscribe(this.fetch); this.subscription = Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => { this.panelTitle = this.output.title || ''; @@ -281,10 +279,9 @@ export class SearchEmbeddable extends Embeddable }); const inspectorRequest = this.inspectorAdaptors.requests.start(title, { description }); inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then((body: any) => { + searchSource.getSearchRequestBody().then((body: Record) => { inspectorRequest.json(body); }); - this.searchScope.isLoading = true; try { @@ -292,7 +289,6 @@ export class SearchEmbeddable extends Embeddable const resp = await searchSource.fetch({ abortSignal: this.abortController.signal, }); - this.searchScope.isLoading = false; // Log response to inspector 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 ebea646a09889a..b5475b2629c703 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,18 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { IPrivate } from 'ui/private'; import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import '../angular/doc_table'; +import { IInjector } from 'ui/chrome'; 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 } from './search_embeddable'; import { SearchInput, SearchOutput } from './types'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; @@ -38,8 +37,15 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< SearchEmbeddable > { public readonly type = SEARCH_EMBEDDABLE_TYPE; + private $injector: IInjector | null; + private getInjector: () => Promise | null; + public isEditable: () => boolean; - constructor(private readonly executeTriggerActions: TExecuteTriggerActions) { + constructor( + private readonly executeTriggerActions: TExecuteTriggerActions, + getInjector: () => Promise, + isEditable: () => boolean + ) { super({ savedObjectMetaData: { name: i18n.translate('kbn.discover.savedSearch.savedObjectName', { @@ -49,10 +55,9 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< getIconForSavedObject: () => 'search', }, }); - } - - public isEditable() { - return getServices().capabilities.discover.save as boolean; + this.$injector = null; + this.getInjector = getInjector; + this.isEditable = isEditable; } public canCreateNew() { @@ -70,20 +75,19 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string; timeRange: TimeRange }, parent?: Container ): Promise { - const $injector = await getServices().getInjector(); + if (!this.$injector) { + this.$injector = await this.getInjector(); + } + const $injector = this.$injector as IInjector; const $compile = $injector.get('$compile'); const $rootScope = $injector.get('$rootScope'); - const searchLoader = $injector.get('savedSearches'); - const editUrl = await getServices().addBasePath( - `/app/kibana${searchLoader.urlFor(savedObjectId)}` - ); - - const Private = $injector.get('Private'); + const filterManager = getServices().filterManager; - const queryFilter = Private(getServices().FilterBarQueryFilterProvider); + const url = await getServices().getSavedSearchUrlById(savedObjectId); + const editUrl = getServices().addBasePath(`/app/kibana${url}`); try { - const savedObject = await searchLoader.get(savedObjectId); + const savedObject = await getServices().getSavedSearchById(savedObjectId); const indexPattern = savedObject.searchSource.getField('index'); return new SearchEmbeddable( { @@ -91,7 +95,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< $rootScope, $compile, editUrl, - queryFilter, + filterManager, editable: getServices().capabilities.discover.save as boolean, indexPatterns: indexPattern ? [indexPattern] : [], }, @@ -109,6 +113,3 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< return new ErrorEmbeddable('Saved searches can only be created from a saved object', input); } } - -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 2d940ad8cba98a..adfa3d5acbf7a3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -18,10 +18,9 @@ */ import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from 'src/plugins/embeddable/public'; -import { StaticIndexPattern } from '../kibana_services'; import { SavedSearch } from '../types'; import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; -import { esFilters, TimeRange, Query } from '../../../../../../plugins/data/public'; +import { esFilters, IIndexPattern, TimeRange, Query } from '../../../../../../plugins/data/public'; export interface SearchInput extends EmbeddableInput { timeRange: TimeRange; @@ -34,7 +33,7 @@ export interface SearchInput extends EmbeddableInput { export interface SearchOutput extends EmbeddableOutput { editUrl: string; - indexPatterns?: StaticIndexPattern[]; + indexPatterns?: IIndexPattern[]; editable: boolean; } diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts new file mode 100644 index 00000000000000..f6982e13d7d039 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -0,0 +1,333 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// inner angular imports +// these are necessary to bootstrap the local angular. +// They can stay even after NP cutover +import angular from 'angular'; +import 'ui/angular-bootstrap'; +import { IPrivate } from 'ui/private'; +import { EuiIcon } from '@elastic/eui'; +// @ts-ignore +import { StateProvider } from 'ui/state_management/state'; +// @ts-ignore +import { EventsProvider } from 'ui/events'; +import { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +import { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +import { createEsService } from 'ui/es'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +// @ts-ignore +import { PrivateProvider } from 'ui/private/private'; +import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; +// @ts-ignore +import { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; +// @ts-ignore +import { registerListenEventListener } from 'ui/directives/listen/listen'; +// @ts-ignore +import { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; +// @ts-ignore +import { FieldNameDirectiveProvider } from 'ui/directives/field_name'; +// @ts-ignore +import { CollapsibleSidebarProvider } from 'ui/collapsible_sidebar/collapsible_sidebar'; +// @ts-ignore +import { CssTruncateProvide } from 'ui/directives/css_truncate'; +// @ts-ignore +import { FixedScrollProvider } from 'ui/fixed_scroll'; +// @ts-ignore +import { DebounceProviderTimeout } from 'ui/directives/debounce/debounce'; +// @ts-ignore +import { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +import { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +import { createRenderCompleteDirective } from 'ui/render_complete/directive'; +// @ts-ignore +import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +import { configureAppAngularModule } from 'ui/legacy_compat'; +import { IndexPatterns } from '../../../../../plugins/data/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { NavigationStart } from '../../../navigation/public'; +import { createDocTableDirective } from './angular/doc_table/doc_table'; +import { createTableHeaderDirective } from './angular/doc_table/components/table_header'; +import { + createToolBarPagerButtonsDirective, + createToolBarPagerTextDirective, +} from './angular/doc_table/components/pager'; +import { createTableRowDirective } from './angular/doc_table/components/table_row'; +import { createPagerFactory } from './angular/doc_table/lib/pager/pager_factory'; +import { createInfiniteScrollDirective } from './angular/doc_table/infinite_scroll'; +import { createDocViewerDirective } from './angular/doc_viewer'; +import { createFieldSearchDirective } from './components/field_chooser/discover_field_search_directive'; +import { createIndexPatternSelectDirective } from './components/field_chooser/discover_index_pattern_directive'; +import { createStringFieldProgressBarDirective } from './components/field_chooser/string_progress_bar'; +// @ts-ignore +import { createFieldChooserDirective } from './components/field_chooser/field_chooser'; + +// @ts-ignore +import { createDiscoverFieldDirective } from './components/field_chooser/discover_field'; +import { DiscoverStartPlugins } from './plugin'; + +/** + * returns the main inner angular module, it contains all the parts of Angular Discover + * needs to render, so in the end the current 'kibana' angular module is no longer necessary + */ +export function getInnerAngularModule(name: string, core: CoreStart, deps: DiscoverStartPlugins) { + const module = initializeInnerAngularModule(name, core, deps.navigation); + configureAppAngularModule(module, core as LegacyCoreStart, true); + return module; +} + +/** + * returns a slimmer inner angular module for embeddable rendering + */ +export function getInnerAngularModuleEmbeddable( + name: string, + core: CoreStart, + deps: DiscoverStartPlugins +) { + const module = initializeInnerAngularModule(name, core, deps.navigation, true); + configureAppAngularModule(module, core as LegacyCoreStart, true); + return module; +} + +let initialized = false; + +export function initializeInnerAngularModule( + name = 'app/discover', + core: CoreStart, + navigation: NavigationStart, + embeddable = false +) { + if (!initialized) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core.uiSettings); + createLocalKbnUrlModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalGlobalStateModule(); + createLocalAppStateModule(); + createLocalStorageModule(); + createElasticSearchModule(); + createIndexPatternsModule(); + createPagerFactoryModule(); + createDocTableModule(); + initialized = true; + } + + if (embeddable) { + return angular + .module(name, [ + 'ngSanitize', + 'react', + 'ui.bootstrap', + 'discoverI18n', + 'discoverPrivate', + 'discoverDocTable', + 'discoverPagerFactory', + 'discoverPersistedState', + ]) + .config(watchMultiDecorator) + .directive('icon', reactDirective => reactDirective(EuiIcon)) + .directive('fieldName', FieldNameDirectiveProvider) + .directive('renderComplete', createRenderCompleteDirective) + .service('debounce', ['$timeout', DebounceProviderTimeout]); + } + + return angular + .module(name, [ + 'ngSanitize', + 'ngRoute', + 'react', + 'ui.bootstrap', + 'elasticsearch', + 'discoverConfig', + 'discoverI18n', + 'discoverPrivate', + 'discoverPersistedState', + 'discoverTopNav', + 'discoverGlobalState', + 'discoverAppState', + 'discoverLocalStorageProvider', + 'discoverIndexPatterns', + 'discoverEs', + 'discoverDocTable', + 'discoverPagerFactory', + ]) + .config(watchMultiDecorator) + .run(registerListenEventListener) + .directive('icon', reactDirective => reactDirective(EuiIcon)) + .directive('kbnAccessibleClick', KbnAccessibleClickProvider) + .directive('fieldName', FieldNameDirectiveProvider) + .directive('collapsibleSidebar', CollapsibleSidebarProvider) + .directive('cssTruncate', CssTruncateProvide) + .directive('fixedScroll', FixedScrollProvider) + .directive('renderComplete', createRenderCompleteDirective) + .directive('discoverFieldSearch', createFieldSearchDirective) + .directive('discoverIndexPatternSelect', createIndexPatternSelectDirective) + .directive('stringFieldProgressBar', createStringFieldProgressBarDirective) + .directive('discoverField', createDiscoverFieldDirective) + .directive('discFieldChooser', createFieldChooserDirective) + .service('debounce', ['$timeout', DebounceProviderTimeout]); +} + +export function createLocalGlobalStateModule() { + angular + .module('discoverGlobalState', [ + 'discoverPrivate', + 'discoverConfig', + 'discoverKbnUrl', + 'discoverPromise', + ]) + .service('globalState', function(Private: IPrivate) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('discoverPersistedState', ['discoverPrivate', 'discoverPromise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('discoverKbnUrl', ['discoverPrivate', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(uiSettings: IUiSettingsClient) { + angular + .module('discoverConfig', ['discoverPrivate']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: (value: string) => { + return uiSettings ? uiSettings.get(value) : undefined; + }, + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('discoverPromise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('discoverPrivate', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('discoverTopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalI18nModule() { + angular + .module('discoverI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createLocalAppStateModule() { + angular + .module('discoverAppState', [ + 'discoverGlobalState', + 'discoverPrivate', + 'discoverConfig', + 'discoverKbnUrl', + 'discoverPromise', + ]) + .service('AppState', function(Private: IPrivate) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: any) { + return Private(AppStateProvider).getAppState; + }) + .service('State', function(Private: any) { + return Private(StateProvider); + }); +} + +function createLocalStorageModule() { + angular + .module('discoverLocalStorageProvider', ['discoverPrivate']) + .service('localStorage', createLocalStorageService('localStorage')) + .service('sessionStorage', createLocalStorageService('sessionStorage')); +} + +const createLocalStorageService = function(type: string) { + return function($window: any) { + return new Storage($window[type]); + }; +}; + +function createElasticSearchModule() { + angular + .module('discoverEs', ['elasticsearch', 'discoverConfig']) + // Elasticsearch client used for requesting data. Connects to the /elasticsearch proxy + .service('es', createEsService); +} + +function createIndexPatternsModule() { + angular.module('discoverIndexPatterns', []).value('indexPatterns', IndexPatterns); +} + +function createPagerFactoryModule() { + angular.module('discoverPagerFactory', []).factory('pagerFactory', createPagerFactory); +} + +function createDocTableModule() { + angular + .module('discoverDocTable', [ + 'discoverKbnUrl', + 'discoverConfig', + 'discoverAppState', + 'discoverPagerFactory', + 'react', + ]) + .directive('docTable', createDocTableDirective) + .directive('kbnTableHeader', createTableHeaderDirective) + .directive('toolBarPagerText', createToolBarPagerTextDirective) + .directive('toolBarPagerText', createToolBarPagerTextDirective) + .directive('kbnTableRow', createTableRowDirective) + .directive('toolBarPagerButtons', createToolBarPagerButtonsDirective) + .directive('kbnInfiniteScroll', createInfiniteScrollDirective) + .directive('docViewer', createDocViewerDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/breadcrumbs.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts rename to src/legacy/core_plugins/kibana/public/discover/helpers/breadcrumbs.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts new file mode 100644 index 00000000000000..b72bd27a31cf97 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts @@ -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 { + Capabilities, + ChromeStart, + CoreStart, + DocLinksStart, + ToastsStart, + IUiSettingsClient, +} from 'kibana/public'; +import * as docViewsRegistry from 'ui/registry/doc_views'; +import chromeLegacy from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +import { FilterManager, TimefilterContract, IndexPatternsContract } from 'src/plugins/data/public'; +// @ts-ignore +import { createSavedSearchesService } from '../saved_searches/saved_searches'; +// @ts-ignore +import { createSavedSearchFactory } from '../saved_searches/_saved_search'; +import { DiscoverStartPlugins } from '../plugin'; +import { EuiUtilsStart } from '../../../../../../plugins/eui_utils/public'; +import { SavedSearch } from '../types'; +import { SharePluginStart } from '../../../../../../plugins/share/public'; + +export interface DiscoverServices { + addBasePath: (path: string) => string; + capabilities: Capabilities; + chrome: ChromeStart; + core: CoreStart; + docLinks: DocLinksStart; + docViewsRegistry: docViewsRegistry.DocViewsRegistry; + eui_utils: EuiUtilsStart; + filterManager: FilterManager; + indexPatterns: IndexPatternsContract; + inspector: unknown; + metadata: { branch: string }; + share: SharePluginStart; + timefilter: TimefilterContract; + toastNotifications: ToastsStart; + // legacy + getSavedSearchById: (id: string) => Promise; + getSavedSearchUrlById: (id: string) => Promise; + uiSettings: IUiSettingsClient; +} + +export async function buildGlobalAngularServices() { + const injector = await chromeLegacy.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + const kbnUrl = injector.get('kbnUrl'); + const SavedSearchFactory = createSavedSearchFactory(Private); + const service = createSavedSearchesService(Private, SavedSearchFactory, kbnUrl, chromeLegacy); + return { + getSavedSearchById: async (id: string) => service.get(id), + getSavedSearchUrlById: async (id: string) => service.urlFor(id), + }; +} + +export async function buildServices(core: CoreStart, plugins: DiscoverStartPlugins, test: false) { + const globalAngularServices = !test + ? await buildGlobalAngularServices() + : { + getSavedSearchById: async (id: string) => void id, + getSavedSearchUrlById: async (id: string) => void id, + State: null, + }; + + return { + ...globalAngularServices, + addBasePath: core.http.basePath.prepend, + capabilities: core.application.capabilities, + chrome: core.chrome, + core, + data: plugins.data, + docLinks: core.docLinks, + docViewsRegistry, + eui_utils: plugins.eui_utils, + filterManager: plugins.data.query.filterManager, + indexPatterns: plugins.data.indexPatterns, + inspector: plugins.inspector, + // @ts-ignore + metadata: core.injectedMetadata.getLegacyMetadata(), + share: plugins.share, + timefilter: plugins.data.query.timefilter.timefilter, + toastNotifications: core.notifications.toasts, + uiSettings: core.uiSettings, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts new file mode 100644 index 00000000000000..bd62460fd68687 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.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 { IIndexPattern } from '../../../../../../plugins/data/common/index_patterns'; + +export function findIndexPatternById( + indexPatterns: IIndexPattern[], + id: string +): IIndexPattern | undefined { + if (!Array.isArray(indexPatterns) || !id) { + return; + } + return indexPatterns.find(o => o.id === id); +} + +/** + * Checks if the given defaultIndex exists and returns + * the first available index pattern id if not + */ +export function getFallbackIndexPatternId( + indexPatterns: IIndexPattern[], + defaultIndex: string = '' +): string { + if (defaultIndex && findIndexPatternById(indexPatterns, defaultIndex)) { + return defaultIndex; + } + return !indexPatterns || !indexPatterns.length || !indexPatterns[0].id ? '' : indexPatterns[0].id; +} + +/** + * A given index pattern id is checked for existence and a fallback is provided if it doesn't exist + * The provided defaultIndex is usually configured in Advanced Settings, if it's also invalid + * the first entry of the given list of Indexpatterns is used + */ +export function getIndexPatternId( + id: string = '', + indexPatterns: IIndexPattern[], + defaultIndex: string = '' +): string { + if (!id || !findIndexPatternById(indexPatterns, id)) { + return getFallbackIndexPatternId(indexPatterns, defaultIndex); + } + return id; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 35e48598f07a80..7f8ca4e96c5aca 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -18,7 +18,9 @@ */ import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects'; import { DiscoverPlugin, DiscoverSetup, DiscoverStart } from './plugin'; +import { start as navigation } from '../../../navigation/public/legacy'; // Core will be looking for this when loading our plugin in the new platform export const plugin: PluginInitializer = ( @@ -27,6 +29,13 @@ export const plugin: PluginInitializer = ( 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); +// Legacy compatiblity part - to be removed at cutover, replaced by a kibana.json file +export const pluginInstance = plugin({} as PluginInitializerContext); +(async () => { + pluginInstance.setup(npSetup.core, npSetup.plugins); + pluginInstance.start(npStart.core, { ...npStart.plugins, navigation }); +})(); + +SavedObjectRegistryProvider.register((savedSearches: any) => { + return savedSearches; +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 5a10e02ba8131f..d13d0dc868a588 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -16,71 +16,40 @@ * 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'; -// @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'; -// @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'; -import { SearchSource } from '../../../../ui/public/courier'; +import { DiscoverServices } from './helpers/build_services'; -const services = { - // new plattform - core: npStart.core, - 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, - share: npStart.plugins.share, - // legacy - docTitle, - docViewsRegistry, - FilterBarQueryFilterProvider, - getInjector: () => { - return chromeLegacy.dangerouslyGetActiveInjector(); - }, - SavedObjectRegistryProvider, - SavedObjectProvider, - SearchSource, - StateProvider, - timefilter, - uiModules, - uiRoutes, - wrapInI18nContext, -}; -export function getServices() { +let angularModule: any = null; +let services: DiscoverServices | null = null; + +/** + * set bootstrapped inner angular module + */ +export function setAngularModule(module: any) { + angularModule = module; +} + +/** + * get boostrapped inner angular module + */ +export function getAngularModule() { + return angularModule; +} + +export function getServices(): DiscoverServices { + if (!services) { + throw new Error('Discover services are not yet available'); + } return services; } -// EXPORT legacy static dependencies +export function setServices(newServices: any) { + services = newServices; +} + +// EXPORT legacy static dependencies, should be migrated when available in a new version; export { angular }; +export { wrapInI18nContext } from 'ui/i18n'; export { buildVislibDimensions } from '../../../visualizations/public'; // @ts-ignore export { callAfterBindingsWorkaround } from 'ui/compat'; @@ -98,24 +67,26 @@ 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 { 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 { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; // EXPORT types export { Vis } from 'ui/vis'; -export { StaticIndexPattern, IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns'; +export { + IndexPatternsContract, + IIndexPattern, + IndexPattern, + IFieldType, +} from '../../../../../plugins/data/public'; 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 index 7c2fb4f118915f..6679e8a3b88263 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -16,12 +16,20 @@ * specific language governing permissions and limitations * under the License. */ - import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; +import angular from 'angular'; import { IUiActionsStart } from 'src/plugins/ui_actions/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { registerFeature } from './helpers/register_feature'; import './kibana_services'; import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embeddable/public'; +import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; +import { setAngularModule, setServices } from './kibana_services'; +import { NavigationStart } from '../../../navigation/public'; +import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public'; +import { buildServices } from './helpers/build_services'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; /** * These are the interfaces with your public contracts. You should export these @@ -30,28 +38,108 @@ import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embed */ export type DiscoverSetup = void; export type DiscoverStart = void; -interface DiscoverSetupPlugins { +export interface DiscoverSetupPlugins { uiActions: IUiActionsStart; embeddable: IEmbeddableSetup; + kibana_legacy: KibanaLegacySetup; } -interface DiscoverStartPlugins { +export interface DiscoverStartPlugins { uiActions: IUiActionsStart; embeddable: IEmbeddableStart; + navigation: NavigationStart; + eui_utils: EuiUtilsStart; + data: DataPublicPluginStart; + share: SharePluginStart; + inspector: any; } +const innerAngularName = 'app/discover'; +const embeddableAngularName = 'app/discoverEmbeddable'; +/** + * Contains Discover, one of the oldest parts of Kibana + * There are 2 kinds of Angular bootstrapped for rendering, additionally to the main Angular + * Discover provides embeddables, those contain a slimmer Angular + */ export class DiscoverPlugin implements Plugin { + private servicesInitialized: boolean = false; + private innerAngularInitialized: boolean = false; + /** + * why are those functions public? they are needed for some mocha tests + * can be removed once all is Jest + */ + public initializeInnerAngular?: () => void; + public initializeServices?: () => void; constructor(initializerContext: PluginInitializerContext) {} setup(core: CoreSetup, plugins: DiscoverSetupPlugins): DiscoverSetup { - registerFeature(); - require('./angular'); + plugins.kibana_legacy.registerLegacyApp({ + id: 'discover', + title: 'Discover', + order: -1004, + euiIconType: 'discoverApp', + mount: async (context, params) => { + if (!this.initializeServices) { + throw Error('Discover plugin method initializeServices is undefined'); + } + if (!this.initializeInnerAngular) { + throw Error('Discover plugin method initializeInnerAngular is undefined'); + } + await this.initializeServices(); + await this.initializeInnerAngular(); + const { renderApp } = await import('./application'); + return renderApp(innerAngularName, params.element); + }, + }); } 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); + // we need to register the application service at setup, but to render it + // there are some start dependencies necessary, for this reason + // initializeInnerAngular + initializeServices are assigned at start and used + // when the application/embeddable is mounted + this.initializeInnerAngular = async () => { + if (this.innerAngularInitialized) { + return; + } + // this is used by application mount and tests + const module = getInnerAngularModule(innerAngularName, core, plugins); + setAngularModule(module); + this.innerAngularInitialized = true; + }; + + this.initializeServices = async (test = false) => { + if (this.servicesInitialized) { + return; + } + const services = await buildServices(core, plugins, test); + setServices(services); + this.servicesInitialized = true; + }; + + this.registerEmbeddable(core, plugins); + registerFeature(); } - stop() {} + /** + * register embeddable with a slimmer embeddable version of inner angular + */ + private async registerEmbeddable(core: CoreStart, plugins: DiscoverStartPlugins) { + const { SearchEmbeddableFactory } = await import('./embeddable'); + const getInjector = async () => { + if (!this.initializeServices) { + throw Error('Discover plugin registerEmbeddable: initializeServices is undefined'); + } + await this.initializeServices(); + getInnerAngularModuleEmbeddable(embeddableAngularName, core, plugins); + const mountpoint = document.createElement('div'); + return angular.bootstrap(mountpoint, [embeddableAngularName]); + }; + const isEditable = () => core.application.capabilities.discover.save as boolean; + + const factory = new SearchEmbeddableFactory( + plugins.uiActions.executeTriggerActions, + getInjector, + isEditable + ); + plugins.embeddable.registerEmbeddableFactory(factory.type, factory); + } } 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 9bbc5baf4fc229..db2b2b5b22af7a 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 @@ -18,13 +18,13 @@ */ import { createLegacyClass } from 'ui/utils/legacy_class'; -import { getServices } from '../kibana_services'; +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -const { uiModules, SavedObjectProvider } = getServices(); +import { uiModules } from 'ui/modules'; const module = uiModules.get('discover/saved_searches', []); -module.factory('SavedSearch', function (Private) { +export function createSavedSearchFactory(Private) { const SavedObject = Private(SavedObjectProvider); createLegacyClass(SavedSearch).inherits(SavedObject); function SavedSearch(id) { @@ -32,7 +32,6 @@ module.factory('SavedSearch', function (Private) { type: SavedSearch.type, mapping: SavedSearch.mapping, searchSource: SavedSearch.searchSource, - id: id, defaults: { title: '', @@ -68,4 +67,6 @@ module.factory('SavedSearch', function (Private) { }; return SavedSearch; -}); +} + +module.factory('SavedSearch', createSavedSearchFactory); 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 deleted file mode 100644 index 9554642c225fd5..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js +++ /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. - */ - -import { getServices } from '../kibana_services'; -import './saved_searches'; - - -getServices().SavedObjectRegistryProvider.register((savedSearches) => { - return savedSearches; -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js index d68b7f0e0d0971..7ebcba903cc7f3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js @@ -18,32 +18,33 @@ */ import './_saved_search'; -import 'ui/notify'; import { uiModules } from 'ui/modules'; import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -const module = uiModules.get('discover/saved_searches'); + // Register this service with the saved object registry so it can be // edited by the object editor. savedObjectManagementRegistry.register({ service: 'savedSearches', - title: 'searches' + title: 'searches', }); -module.service('savedSearches', function (Private, SavedSearch, kbnUrl, chrome) { +export function createSavedSearchesService(Private, SavedSearch, kbnUrl, chrome) { const savedObjectClient = Private(SavedObjectsClientProvider); const savedSearchLoader = new SavedObjectLoader(SavedSearch, kbnUrl, chrome, savedObjectClient); // Customize loader properties since adding an 's' on type doesn't work for type 'search' . savedSearchLoader.loaderProperties = { name: 'searches', noun: 'Saved Search', - nouns: 'saved searches' + nouns: 'saved searches', }; - savedSearchLoader.urlFor = function (id) { + savedSearchLoader.urlFor = (id) => { return kbnUrl.eval('#/discover/{{id}}', { id: id }); }; return savedSearchLoader; -}); +} +const module = uiModules.get('discover/saved_searches'); +module.service('savedSearches', createSavedSearchesService); 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 f69bb654e65df4..780c51c438739c 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 @@ -28,6 +28,7 @@ import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom' import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { getServices } from '../kibana_services'; +import { npSetup } from 'ui/new_platform'; export function HomeApp({ directories }) { const { @@ -39,8 +40,9 @@ export function HomeApp({ directories }) { setOptInNoticeSeen, }, } = getServices(); + const { cloud } = npSetup.plugins; + const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); - const isCloudEnabled = getInjected('isCloudEnabled', false); const apmUiEnabled = getInjected('apmUiEnabled', true); const mlEnabled = getInjected('mlEnabled', false); const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js index 086fa5a0591216..567bb3f83f22c8 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -67,7 +67,7 @@ class TutorialUi extends React.Component { } } - componentWillMount() { + UNSAFE_componentWillMount() { this._isMounted = true; } diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index 6494cc79640e1c..bd3a0b38ec3f0e 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -23,7 +23,6 @@ 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'; export const trackUiMetric = createUiStatsReporter('Kibana_home'); @@ -74,6 +73,6 @@ let copiedLegacyCatalogue = false; }, }); instance.start(npStart.core, { - data, + data: npStart.plugins.data, }); })(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 5ef6e019db0424..3ec095f4f26bf8 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -25,7 +25,7 @@ import { NotificationsSetup, OverlayStart, SavedObjectsClientContract, - UiSettingsClientContract, + IUiSettingsClient, UiSettingsState, } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; @@ -50,7 +50,7 @@ export interface HomeKibanaServices { getInjected: (name: string, defaultValue?: any) => unknown; chrome: ChromeStart; telemetryOptInProvider: any; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; http: HttpStart; savedObjectsClient: SavedObjectsClientContract; toastNotifications: NotificationsSetup['toasts']; diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index bb0e7e3611616c..fc1747d71d069e 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -20,7 +20,7 @@ import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; -import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { setServices } from './kibana_services'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; @@ -31,7 +31,7 @@ export interface LegacyAngularInjectedDependencies { } export interface HomePluginStartDependencies { - data: DataStart; + data: DataPublicPluginStart; } export interface HomePluginSetupDependencies { @@ -58,7 +58,7 @@ export interface HomePluginSetupDependencies { } export class HomePlugin implements Plugin { - private dataStart: DataStart | null = null; + private dataStart: DataPublicPluginStart | null = null; private savedObjectsClient: any = null; setup( @@ -85,7 +85,7 @@ export class HomePlugin implements Plugin { uiSettings: core.uiSettings, addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, - indexPatternService: this.dataStart!.indexPatterns.indexPatterns, + indexPatternService: this.dataStart!.indexPatterns, ...angularDependencies, }); const { renderApp } = await import('./render_app'); diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/activemq_logs/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/activemq_logs/screenshot.png new file mode 100644 index 00000000000000..3b75889a1bb130 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/activemq_logs/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/azure_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/azure_metrics/screenshot.png new file mode 100644 index 00000000000000..22136049b494ad Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/azure_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/azure.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/azure.svg new file mode 100644 index 00000000000000..f8df12ba05c508 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/azure.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 7a6a3ca1d01d01..611fe613ad99c7 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -1,8 +1,6 @@ @import 'src/legacy/ui/public/styles/styling_constants'; // Elastic charts -@import '@elastic/eui/src/components/tool_tip/variables'; -@import '@elastic/eui/src/components/tool_tip/mixins'; @import '@elastic/charts/dist/theme'; @import '@elastic/eui/src/themes/charts/theme'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 98def2252b75c7..0045819257018e 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -23,12 +23,12 @@ import chrome from 'ui/chrome'; import routes from 'ui/routes'; import { uiModules } from 'ui/modules'; +import { npSetup } from 'ui/new_platform'; // import the uiExports that we want to "use" import 'uiExports/home'; import 'uiExports/visTypes'; -import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; import 'uiExports/fieldFormatEditors'; @@ -59,11 +59,13 @@ import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; import { localApplicationService } from './local_application_service'; + +npSetup.plugins.kibana_legacy.forwardApp('doc', 'discover', { keepPrefix: true }); +npSetup.plugins.kibana_legacy.forwardApp('context', 'discover', { keepPrefix: true }); localApplicationService.attachToAngular(routes); routes.enable(); - routes .otherwise({ redirectTo: `/${chrome.getInjected('kbnDefaultAppId', 'discover')}` diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js index df90abce00e48d..d0441ce3ceb8b3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js @@ -61,7 +61,7 @@ export class IndicesList extends Component { this.pager = new Pager(props.indices.length, this.state.perPage, this.state.page); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.indices.length !== this.props.indices.length) { this.pager.setTotalItems(nextProps.indices.length); this.resetPageTo0(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js index 617262c13b034f..4764b516dffec4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js @@ -75,7 +75,7 @@ export class StepIndexPattern extends Component { this.lastQuery = null; } - async componentWillMount() { + async UNSAFE_componentWillMount() { this.fetchExistingIndexPatterns(); if (this.state.query) { this.lastQuery = this.state.query; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js index 5677d2293a725c..1f9d80440f6cbc 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js @@ -43,6 +43,7 @@ const indexPatternsService = { make: async () => ({ fieldsFetcher: { fetch: noop, + fetchForWildcard: noop, }, }), }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js index 5cb93a36cdd184..1e894664c2bf18 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js @@ -50,8 +50,6 @@ export class StepTimeField extends Component { constructor(props) { super(props); - const { getIndexPatternType, getIndexPatternName } = props.indexPatternCreationType; - this.state = { error: '', timeFields: [], @@ -61,8 +59,8 @@ export class StepTimeField extends Component { isFetchingTimeFields: false, isCreating: false, indexPatternId: '', - indexPatternType: getIndexPatternType(), - indexPatternName: getIndexPatternName(), + indexPatternType: props.indexPatternCreationType.getIndexPatternType(), + indexPatternName: props.indexPatternCreationType.getIndexPatternName(), }; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js index a1f302dc87f6c1..566277802174a9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js @@ -65,7 +65,7 @@ export class CreateIndexPatternWizard extends Component { }; } - async componentWillMount() { + async UNSAFE_componentWillMount() { this.fetchData(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js index b500f5c79e98bd..833ca8467292e3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js @@ -21,7 +21,7 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects'; import uiRoutes from 'ui/routes'; import angularTemplate from './angular_template.html'; import 'ui/index_patterns'; -import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../../management/public/legacy'; import { getCreateBreadcrumbs } from '../breadcrumbs'; import { renderCreateIndexPatternWizard, destroyCreateIndexPatternWizard } from './render'; @@ -35,8 +35,9 @@ uiRoutes.when('/management/kibana/index_pattern', { const Private = $injector.get('Private'); $scope.$$postDigest(() => { const $routeParams = $injector.get('$routeParams'); - const indexPatternCreationProvider = Private(IndexPatternCreationFactory)($routeParams.type); - const indexPatternCreationType = indexPatternCreationProvider.getType(); + const indexPatternCreationType = managementSetup.indexPattern.creation.getType( + $routeParams.type + ); const services = { config: $injector.get('config'), es: $injector.get('es'), @@ -52,12 +53,9 @@ uiRoutes.when('/management/kibana/index_pattern', { const initialQuery = $routeParams.id ? decodeURIComponent($routeParams.id) : undefined; - renderCreateIndexPatternWizard( - initialQuery, - services - ); + renderCreateIndexPatternWizard(initialQuery, services); }); $scope.$on('$destroy', destroyCreateIndexPatternWizard); - } + }, }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js index c9ea1e12c58fdc..5f0994abc11e48 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js @@ -17,9 +17,7 @@ * under the License. */ -import { setup as data } from '../../../../../../../data/public/legacy'; -const { FieldImpl: Field } = data.indexPatterns.__LEGACY; - +import { Field } from '../../../../../../../../../plugins/data/public'; import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors'; import { docTitle } from 'ui/doc_title'; import { KbnUrlProvider } from 'ui/url'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index 6ae84b9c641c24..150fae6e87ddec 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -28,7 +28,7 @@ import uiRoutes from 'ui/routes'; import { uiModules } from 'ui/modules'; import template from './edit_index_pattern.html'; import { fieldWildcardMatcher } from 'ui/field_wildcard'; -import { IndexPatternListFactory } from 'ui/management/index_pattern_list'; +import { setup as managementSetup } from '../../../../../../management/public/legacy'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { SourceFiltersTable } from './source_filters_table'; @@ -58,13 +58,17 @@ function updateSourceFiltersTable($scope, $state) { filterFilter={$scope.fieldFilter} fieldWildcardMatcher={$scope.fieldWildcardMatcher} onAddOrRemoveFilter={() => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, $scope.indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + $scope.indexPatternListProvider + ); $scope.refreshFilters(); $scope.$apply(); }} /> , - node, + node ); }); } else { @@ -77,7 +81,6 @@ function destroySourceFiltersTable() { node && unmountComponentAtNode(node); } - function updateScriptedFieldsTable($scope, $state) { if ($state.tab === 'scriptedFields') { $scope.$$postDigest(() => { @@ -100,13 +103,17 @@ function updateScriptedFieldsTable($scope, $state) { getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route), }} onRemoveField={() => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, $scope.indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + $scope.indexPatternListProvider + ); $scope.refreshFilters(); $scope.$apply(); }} /> , - node, + node ); }); } else { @@ -144,7 +151,7 @@ function updateIndexedFieldsTable($scope, $state) { }} /> , - node, + node ); }); } else { @@ -157,34 +164,36 @@ function destroyIndexedFieldsTable() { node && unmountComponentAtNode(node); } -uiRoutes - .when('/management/kibana/index_patterns/:indexPatternId', { - template, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) { - return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)) - .catch(redirectWhenMissing('/management/kibana/index_patterns')); - } +uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', { + template, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) { + return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( + redirectWhenMissing('/management/kibana/index_patterns') + ); }, - }); + }, +}); -uiModules.get('apps/management') +uiModules + .get('apps/management') .controller('managementIndexPatternsEdit', function ( $scope, $location, $route, Promise, config, indexPatterns, Private, AppState, confirmModal) { const $state = $scope.state = new AppState(); - const indexPatternListProvider = Private(IndexPatternListFactory)(); $scope.fieldWildcardMatcher = (...args) => fieldWildcardMatcher(...args, config.get('metaFields')); $scope.editSectionsProvider = Private(IndicesEditSectionsProvider); $scope.kbnUrl = Private(KbnUrlProvider); $scope.indexPattern = $route.current.locals.indexPattern; - $scope.indexPatternListProvider = indexPatternListProvider; - $scope.indexPattern.tags = indexPatternListProvider.getIndexPatternTags( + $scope.indexPatternListProvider = managementSetup.indexPattern.list; + $scope.indexPattern.tags = managementSetup.indexPattern.list.getIndexPatternTags( $scope.indexPattern, $scope.indexPattern.id === config.get('defaultIndex') ); - $scope.getFieldInfo = indexPatternListProvider.getFieldInfo; + $scope.getFieldInfo = managementSetup.indexPattern.list.getFieldInfo.bind( + managementSetup.indexPattern.list + ); docTitle.change($scope.indexPattern.title); const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { @@ -192,7 +201,11 @@ uiModules.get('apps/management') }); $scope.$watch('indexPattern.fields', function () { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + managementSetup.indexPattern.list + ); $scope.refreshFilters(); $scope.fields = $scope.indexPattern.getNonScriptedFields(); updateIndexedFieldsTable($scope, $state); @@ -231,26 +244,26 @@ uiModules.get('apps/management') }); $scope.$watchCollection('indexPattern.fields', function () { - $scope.conflictFields = $scope.indexPattern.fields - .filter(field => field.type === 'conflict'); + $scope.conflictFields = $scope.indexPattern.fields.filter(field => field.type === 'conflict'); }); $scope.refreshFields = function () { const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', { - defaultMessage: 'This action resets the popularity counter of each field.' + defaultMessage: 'This action resets the popularity counter of each field.', }); const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { defaultMessage: 'Refresh' }), + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { + defaultMessage: 'Refresh', + }), onConfirm: async () => { await $scope.indexPattern.init(true); $scope.fields = $scope.indexPattern.getNonScriptedFields(); }, - title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { defaultMessage: 'Refresh field list?' }) + title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { + defaultMessage: 'Refresh field list?', + }), }; - confirmModal( - confirmMessage, - confirmModalOptions - ); + confirmModal(confirmMessage, confirmModalOptions); }; $scope.removePattern = function () { @@ -271,9 +284,13 @@ uiModules.get('apps/management') } const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { defaultMessage: 'Delete' }), + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { + defaultMessage: 'Delete', + }), onConfirm: doRemove, - title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { defaultMessage: 'Delete index pattern?' }) + title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { + defaultMessage: 'Delete index pattern?', + }), }; confirmModal('', confirmModalOptions); }; @@ -285,7 +302,8 @@ uiModules.get('apps/management') $scope.setIndexPatternsTimeField = function (field) { if (field.type !== 'date') { const errorMessage = i18n.translate('kbn.management.editIndexPattern.notDateErrorMessage', { - defaultMessage: 'That field is a {fieldType} not a date.', values: { fieldType: field.type } + defaultMessage: 'That field is a {fieldType} not a date.', + values: { fieldType: field.type }, }); toastNotifications.addDanger(errorMessage); return; @@ -295,12 +313,16 @@ uiModules.get('apps/management') }; $scope.$watch('fieldFilter', () => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + managementSetup.indexPattern.list + ); if ($scope.fieldFilter === undefined) { return; } - switch($state.tab) { + switch ($state.tab) { case 'indexedFields': updateIndexedFieldsTable($scope, $state); case 'scriptedFields': diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js index cb1c316c8af9be..b32c325e936535 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js @@ -47,7 +47,7 @@ export class IndexedFieldsTable extends Component { }; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.fields !== this.props.fields) { this.setState({ fields: this.mapFields(nextProps.fields) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js index d91f4836ee1d8f..c7677955a8069b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js @@ -59,7 +59,7 @@ export class ScriptedFieldsTable extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.fetchFields(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js index ba93485a1739a0..1201e23c48e443 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js @@ -58,7 +58,7 @@ export class SourceFiltersTable extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.updateFilters(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index 272544fa036cce..5935afec1dd707 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,8 +18,7 @@ */ import { management } from 'ui/management'; -import { IndexPatternListFactory } from 'ui/management/index_pattern_list'; -import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../management/public/legacy'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -28,7 +27,10 @@ import indexTemplate from './index.html'; import indexPatternListTemplate from './list.html'; import { IndexPatternTable } from './index_pattern_table'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { + FeatureCatalogueRegistryProvider, + FeatureCatalogueCategory, +} from 'ui/registry/feature_catalogue'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { UICapabilitiesProvider } from 'ui/capabilities/react'; @@ -39,11 +41,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; const INDEX_PATTERN_LIST_DOM_ELEMENT_ID = 'indexPatternListReact'; -export function updateIndexPatternList( - indexPatterns, - kbnUrl, - indexPatternCreationOptions, -) { +export function updateIndexPatternList(indexPatterns, kbnUrl, indexPatternCreationOptions) { const node = document.getElementById(INDEX_PATTERN_LIST_DOM_ELEMENT_ID); if (!node) { return; @@ -59,7 +57,7 @@ export function updateIndexPatternList( /> , - node, + node ); } @@ -72,55 +70,56 @@ const indexPatternsResolutions = { indexPatterns: function (Private) { const savedObjectsClient = Private(SavedObjectsClientProvider); - return savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'type'], - perPage: 10000 - }).then(response => response.savedObjects); - } + return savedObjectsClient + .find({ + type: 'index-pattern', + fields: ['title', 'type'], + perPage: 10000, + }) + .then(response => response.savedObjects); + }, }; // add a dependency to all of the subsection routes -uiRoutes - .defaults(/management\/kibana\/(index_patterns|index_pattern)/, { - resolve: indexPatternsResolutions, - requireUICapability: 'management.kibana.index_patterns', - badge: uiCapabilities => { - if (uiCapabilities.indexPatterns.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.management.indexPatterns.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.management.indexPatterns.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save index patterns', - }), - iconType: 'glasses' - }; +uiRoutes.defaults(/management\/kibana\/(index_patterns|index_pattern)/, { + resolve: indexPatternsResolutions, + requireUICapability: 'management.kibana.index_patterns', + badge: uiCapabilities => { + if (uiCapabilities.indexPatterns.save) { + return undefined; } - }); -uiRoutes - .when('/management/kibana/index_patterns', { - template: indexPatternListTemplate, - k7Breadcrumbs: getListBreadcrumbs - }); + return { + text: i18n.translate('kbn.management.indexPatterns.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.management.indexPatterns.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save index patterns', + }), + iconType: 'glasses', + }; + }, +}); + +uiRoutes.when('/management/kibana/index_patterns', { + template: indexPatternListTemplate, + k7Breadcrumbs: getListBreadcrumbs, +}); // wrapper directive, which sets some global stuff up like the left nav -uiModules.get('apps/management') - .directive('kbnManagementIndexPatterns', function ($route, config, kbnUrl, Private) { +uiModules + .get('apps/management') + .directive('kbnManagementIndexPatterns', function ($route, config, kbnUrl) { return { restrict: 'E', transclude: true, template: indexTemplate, link: async function ($scope) { - const indexPatternListProvider = Private(IndexPatternListFactory)(); - const indexPatternCreationProvider = Private(IndexPatternCreationFactory)(); - const indexPatternCreationOptions = await indexPatternCreationProvider.getIndexPatternCreationOptions((url) => { - $scope.$evalAsync(() => kbnUrl.change(url)); - }); + const indexPatternCreationOptions = await managementSetup.indexPattern.creation.getIndexPatternCreationOptions( + url => { + $scope.$evalAsync(() => kbnUrl.change(url)); + } + ); const renderList = () => { $scope.indexPatternList = @@ -129,7 +128,7 @@ uiModules.get('apps/management') const id = pattern.id; const title = pattern.get('title'); const isDefault = $scope.defaultIndex === id; - const tags = indexPatternListProvider.getIndexPatternTags( + const tags = managementSetup.indexPattern.list.getIndexPatternTags( pattern, isDefault ); @@ -165,25 +164,30 @@ uiModules.get('apps/management') $scope.$watch('defaultIndex', () => renderList()); config.bindToScope($scope, 'defaultIndex'); $scope.$apply(); - } + }, }; }); management.getSection('kibana').register('index_patterns', { - display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { defaultMessage: 'Index Patterns' }), + display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { + defaultMessage: 'Index Patterns', + }), order: 0, - url: '#/management/kibana/index_patterns/' + url: '#/management/kibana/index_patterns/', }); FeatureCatalogueRegistryProvider.register(() => { return { id: 'index_patterns', - title: i18n.translate('kbn.management.indexPatternHeader', { defaultMessage: 'Index Patterns' }), - description: i18n.translate('kbn.management.indexPatternLabel', - { defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.' }), + title: i18n.translate('kbn.management.indexPatternHeader', { + defaultMessage: 'Index Patterns', + }), + description: i18n.translate('kbn.management.indexPatternLabel', { + defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.', + }), icon: 'indexPatternApp', path: '/app/kibana#/management/kibana/index_patterns', showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN + category: FeatureCatalogueCategory.ADMIN, }; }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index 5956b6c306b0e5..1c3666ac0980c6 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -19,6 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../../../../../management/public/np_ready/mocks'; import { Query } from '@elastic/eui'; import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; @@ -29,6 +30,11 @@ import { extractExportDetails } from '../../../lib/extract_export_details'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); +jest.mock('../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + jest.mock('../../../lib/find_objects', () => ({ findObjects: jest.fn(), })); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js index e465149b301dcf..97c0d5b89d657b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; - +import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; import { Flyout } from '../flyout'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -48,6 +48,11 @@ jest.mock('../../../../../lib/resolve_saved_objects', () => ({ saveObjects: jest.fn(), })); +jest.mock('../../../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + jest.mock('ui/notify', () => ({})); const defaultProps = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js index 278f7de38b19d9..ee9fb70e31fb26 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js @@ -58,11 +58,11 @@ export class Relationships extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.getRelationshipData(); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.savedObject.id !== this.props.savedObject.id) { this.getRelationshipData(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js index d3f5fb19452548..fba249670ce60c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js @@ -21,6 +21,7 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { keyCodes } from '@elastic/eui/lib/services'; +import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -28,6 +29,11 @@ jest.mock('ui/chrome', () => ({ addBasePath: () => '', })); +jest.mock('../../../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + import { Table } from '../table'; const defaultProps = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js index 43cf8c2a232867..eeddc390037a6a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js @@ -18,7 +18,7 @@ */ import chrome from 'ui/chrome'; -import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management'; +import { setup as managementSetup } from '../../../../../../../../../management/public/legacy'; import React, { PureComponent, Fragment } from 'react'; import PropTypes from 'prop-types'; @@ -79,7 +79,7 @@ export class Table extends PureComponent { constructor(props) { super(props); - this.extraActions = SavedObjectsManagementActionRegistry.get(); + this.extraActions = managementSetup.savedObjects.registry.get(); } onChange = ({ query, error }) => { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js index 162fae33ba19e5..22c3e372230f52 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js @@ -17,7 +17,6 @@ * under the License. */ -import { SavedObjectNotFound } from '../../../../../../../../plugins/kibana_utils/public'; import { i18n } from '@kbn/i18n'; async function getSavedObject(doc, services) { @@ -255,7 +254,8 @@ export async function resolveSavedObjects(savedObjects, overwriteAll, services, importedObjectCount++; } } catch (error) { - if (error instanceof SavedObjectNotFound) { + + if (error.constructor.name === 'SavedObjectNotFound') { if (error.savedObjectType === 'index-pattern') { conflictedIndexPatterns.push({ obj, doc: searchDoc }); } else { @@ -275,7 +275,7 @@ export async function resolveSavedObjects(savedObjects, overwriteAll, services, importedObjectCount++; } } catch (error) { - const isIndexPatternNotFound = error instanceof SavedObjectNotFound && + const isIndexPatternNotFound = error.constructor.name === 'SavedObjectNotFound' && error.savedObjectType === 'index-pattern'; if (isIndexPatternNotFound && obj.savedSearchId) { conflictedSavedObjectsLinkedToSavedSearches.push(obj); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js index c2dabdffb2cd21..525f8495027c17 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js @@ -81,7 +81,7 @@ export class AdvancedSettings extends Component { }, {}); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { config } = nextProps; const { query } = this.state; 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 a953d09906ed11..c790930e32aa0f 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 @@ -76,7 +76,7 @@ export class Field extends PureComponent { this.changeImageForm = null; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { unsavedValue } = this.state; const { type, value, defVal } = nextProps.setting; const editableValue = this.getEditableValue(type, value, defVal); diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts new file mode 100644 index 00000000000000..7684f982de7e02 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -0,0 +1,193 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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, { IModule } from 'angular'; +import { EuiConfirmModal } from '@elastic/eui'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; + +import { AppMountContext, LegacyCoreStart } from 'kibana/public'; +import { + AppStateProvider, + AppState, + configureAppAngularModule, + confirmModalFactory, + createTopNavDirective, + createTopNavHelper, + EventsProvider, + GlobalStateProvider, + KbnUrlProvider, + RedirectWhenMissingProvider, + IPrivate, + PersistedState, + PrivateProvider, + PromiseServiceCreator, + StateManagementConfigProvider, +} from './legacy_imports'; +import { NavigationStart } from '../../../navigation/public'; + +// @ts-ignore +import { initVisualizeApp } from './legacy_app'; +import { VisualizeKibanaServices } from './kibana_services'; + +let angularModuleInstance: IModule | null = null; + +export const renderApp = async ( + element: HTMLElement, + appBasePath: string, + deps: VisualizeKibanaServices +) => { + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); + // custom routing stuff + initVisualizeApp(angularModuleInstance, deps); + } + const $injector = mountVisualizeApp(appBasePath, element); + return () => $injector.get('$rootScope').$destroy(); +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/visualize'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountVisualizeApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalConfirmModalModule(); + + const visualizeAngularModule: IModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'app/visualize/Config', + 'app/visualize/I18n', + 'app/visualize/Private', + 'app/visualize/PersistedState', + 'app/visualize/TopNav', + 'app/visualize/State', + 'app/visualize/ConfirmModal', + ]); + return visualizeAngularModule; +} + +function createLocalConfirmModalModule() { + angular + .module('app/visualize/ConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalStateModule() { + angular + .module('app/visualize/State', [ + 'app/visualize/Private', + 'app/visualize/Config', + 'app/visualize/KbnUrl', + 'app/visualize/Promise', + 'app/visualize/PersistedState', + ]) + .factory('AppState', function(Private: IPrivate) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: IPrivate) { + return Private(AppStateProvider).getAppState; + }) + .service('globalState', function(Private: IPrivate) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('app/visualize/PersistedState', ['app/visualize/Private', 'app/visualize/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('app/visualize/Config', ['app/visualize/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('app/visualize/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('app/visualize/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalI18nModule() { + angular + .module('app/visualize/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} 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 bf9ac9b9bbe361..6190b92c9be3e1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html @@ -39,7 +39,7 @@ show-search-bar="true" show-query-bar="true" show-query-input="showQueryInput()" - show-filter-bar="showFilterBar() && chrome.getVisible()" + show-filter-bar="showFilterBar() && isVisible" show-date-picker="showQueryBarTimePicker()" show-auto-refresh-only="!showQueryBarTimePicker()" query="state.query" @@ -67,7 +67,7 @@ --> savedVisualizations.get($route.current.params)) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); - } - return savedVis; - }) - .catch(redirectWhenMissing({ - '*': '/visualize' - })); - } - } - }) - .when(`${VisualizeConstants.EDIT_PATH}/:id`, { - template: editorTemplate, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) - .then(() => savedVisualizations.get($route.current.params.id)) - .then((savedVis) => { - chrome.recentlyAccessed.add( - savedVis.getFullPath(), - savedVis.title, - savedVis.id - ); - return savedVis; - }) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis).catch(() => savedVis); - } - return savedVis; - }) - .catch( - redirectWhenMissing({ - visualization: '/visualize', - search: '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern': - '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern-field': - '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - }) - ); - } - } - }); +import { getServices } from '../kibana_services'; -uiModules - .get('app/visualize', [ - 'kibana/url' - ]) - .directive('visualizeApp', function () { +export function initEditorDirective(app, deps) { + app.directive('visualizeApp', function () { return { restrict: 'E', controllerAs: 'visualizeApp', - controller: VisEditor, + controller: VisualizeAppController, }; }); -function VisEditor( + initVisEditorDirective(app, deps); + initVisualizationDirective(app, deps); +} + +function VisualizeAppController( $scope, $element, $route, @@ -155,20 +70,42 @@ function VisEditor( $window, $injector, $timeout, - indexPatterns, kbnUrl, redirectWhenMissing, - Private, Promise, - config, kbnBaseUrl, - localStorage, + getAppState, + globalState, ) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); - + const { + indexPatterns, + localStorage, + visualizeCapabilities, + share, + data: { + query: { + filterManager, + timefilter: { timefilter }, + }, + }, + toastNotifications, + legacyChrome, + chrome, + getBasePath, + core: { docLinks }, + savedQueryService, + uiSettings, + } = getServices(); + + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); + const queryFilter = filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; + const _applyVis = () => { + $scope.$apply(); + }; + // This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js. + savedVis.vis.on('apply', _applyVis); // vis is instance of src/legacy/ui/public/vis/vis.js. // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { vis, searchSource } = savedVis; @@ -179,7 +116,7 @@ function VisEditor( dirty: !savedVis.id }; - $scope.topNavMenu = [...(capabilities.visualize.save ? [{ + $scope.topNavMenu = [...(visualizeCapabilities.save ? [{ id: 'save', label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { @@ -248,8 +185,8 @@ function VisEditor( share.toggleShareContextMenu({ anchorElement, allowEmbed: true, - allowShortUrl: capabilities.visualize.createShortUrl, - shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), + allowShortUrl: visualizeCapabilities.createShortUrl, + shareableUrl: unhashUrl(window.location.href), objectId: savedVis.id, objectType: 'visualization', sharingData: { @@ -297,7 +234,7 @@ function VisEditor( let stateMonitor; if (savedVis.id) { - docTitle.change(savedVis.title); + chrome.docTitle.change(savedVis.title); } // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. @@ -308,7 +245,7 @@ function VisEditor( linked: !!savedVis.savedSearchId, query: searchSource.getOwnField('query') || { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }, filters: searchSource.getOwnField('filter') || [], vis: savedVisState @@ -347,9 +284,9 @@ function VisEditor( queryFilter.setFilters(filters); }; - $scope.showSaveQuery = capabilities.visualize.saveQuery; + $scope.showSaveQuery = visualizeCapabilities.saveQuery; - $scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => { + $scope.$watch(() => visualizeCapabilities.saveQuery, (newCapability) => { $scope.showSaveQuery = newCapability; }); @@ -457,13 +394,15 @@ function VisEditor( } })); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', () => { if ($scope._handler) { $scope._handler.destroy(); } savedVis.destroy(); stateMonitor.destroy(); + filterStateManager.destroy(); subscriptions.unsubscribe(); + $scope.vis.off('apply', _applyVis); }); @@ -505,7 +444,7 @@ function VisEditor( delete $state.savedQuery; $state.query = { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }; queryFilter.removeAll(); $state.save(); @@ -591,14 +530,14 @@ 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. - chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + legacyChrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); 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.docTitle.change(savedVis.lastSavedTitle); chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); savedVis.vis.title = savedVis.title; savedVis.vis.description = savedVis.description; @@ -663,7 +602,7 @@ function VisEditor( vis.type.feedbackMessage; }; - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); init(); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js index 198fbe19a0b7aa..d3651735c1a1d8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js @@ -17,13 +17,8 @@ * under the License. */ -import { getServices } from '../kibana_services'; - -const { embeddables, uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEmbedded', function (Private, $timeout, getAppState) { +export function initVisualizationDirective(app, deps) { + app.directive('visualizationEmbedded', function ($timeout, getAppState) { return { restrict: 'E', @@ -37,7 +32,7 @@ uiModules link: function ($scope, element) { $scope.renderFunction = async () => { if (!$scope._handler) { - $scope._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { + $scope._handler = await deps.embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { timeRange: $scope.timeRange, filters: $scope.filters || [], query: $scope.query, @@ -66,3 +61,4 @@ uiModules } }; }); +} 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 ead77e6bc41d50..bc6d4d4c484665 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,15 +17,8 @@ * under the License. */ -import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; - -const { uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEditor', function (Private, $timeout, getAppState) { - const editorTypes = Private(VisEditorTypesRegistryProvider); - +export function initVisEditorDirective(app, deps) { + app.directive('visualizationEditor', function ($timeout, getAppState) { return { restrict: 'E', scope: { @@ -38,7 +31,8 @@ uiModules link: function ($scope, element) { const editorType = $scope.savedObj.vis.type.editor; const Editor = typeof editorType === 'function' ? editorType : - editorTypes.find(editor => editor.key === editorType); + deps.editorTypes.find(editor => editor.key === editorType); + const editor = new Editor(element[0], $scope.savedObj); $scope.renderFunction = () => { @@ -62,3 +56,4 @@ uiModules } }; }); +} 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 065feae0455972..d8792a761b1860 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,8 +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 b6d5adcd98bd02..7fe3678bb1f770 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,23 +17,20 @@ * under the License. */ -import { - getServices, - getFromSavedObject, - StaticIndexPattern, - VisSavedObject, -} from '../kibana_services'; +import { npStart } from 'ui/new_platform'; -const { savedObjectsClient, uiSettings } = getServices(); +import { VisSavedObject } from './visualize_embeddable'; +import { indexPatterns, IIndexPattern } from '../../../../../../plugins/data/public'; export async function getIndexPattern( savedVis: VisSavedObject -): Promise { +): Promise { if (savedVis.vis.type.name !== 'metrics') { return savedVis.vis.indexPattern; } - const defaultIndex = uiSettings.get('defaultIndex'); + const savedObjectsClient = npStart.core.savedObjects.client; + const defaultIndex = npStart.core.uiSettings.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ @@ -42,10 +39,10 @@ export async function getIndexPattern( search: `"${savedVis.vis.params.index_pattern}"`, searchFields: ['title'], }); - const [indexPattern] = indexPatternObjects.savedObjects.map(getFromSavedObject); + const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject); return indexPattern; } const savedObject = await savedObjectsClient.get('index-pattern', defaultIndex); - return getFromSavedObject(savedObject); + return indexPatterns.getFromSavedObject(savedObject); } 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 a2b46dab1ef33b..7ab60f8867c38d 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,7 +18,6 @@ */ import _, { forEach } from 'lodash'; -import { StaticIndexPattern } from 'ui/index_patterns'; import { PersistedState } from 'ui/persisted_state'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; @@ -33,6 +32,7 @@ import { IExpressionLoaderParams } from 'src/plugins/expressions/public'; import { SearchSourceContract } from '../../../../../ui/public/courier'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { + IIndexPattern, TimeRange, Query, onlyDisabledFiltersChanged, @@ -61,7 +61,7 @@ export interface VisSavedObject extends SavedObject { export interface VisualizeEmbeddableConfiguration { savedVisualization: VisSavedObject; - indexPatterns?: StaticIndexPattern[]; + indexPatterns?: IIndexPattern[]; editUrl: string; editable: boolean; appState?: AppState; @@ -81,7 +81,7 @@ export interface VisualizeInput extends EmbeddableInput { export interface VisualizeOutput extends EmbeddableOutput { editUrl: string; - indexPatterns?: StaticIndexPattern[]; + indexPatterns?: IIndexPattern[]; savedObjectId: string; visTypeName: string; } @@ -149,8 +149,6 @@ export class VisualizeEmbeddable extends Embeddable { this.handleChanges(); @@ -377,6 +375,8 @@ export class VisualizeEmbeddable extends Embeddable { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 15ad9a33232ef5..a377dafe9e5123 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -17,36 +17,48 @@ * under the License. */ +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableActions'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; + import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; + import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; +import { + EmbeddableFactory, + ErrorEmbeddable, + Container, + EmbeddableOutput, +} from '../../../../../../plugins/embeddable/public'; +import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { showNewVisModal } from '../wizard'; import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; -import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; -import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; - import { - getServices, - Container, - EmbeddableFactory, - EmbeddableOutput, - ErrorEmbeddable, + VisualizeEmbeddable, + VisualizeInput, + VisualizeOutput, VisSavedObject, -} from '../kibana_services'; - -const { - addBasePath, - capabilities, - embeddable, - getInjector, - uiSettings, - visualizations, -} = getServices(); +} from './visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; +import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -96,7 +108,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (uiSettings.get('visualize:enableLabs')) { + if (npStart.core.uiSettings.get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; @@ -108,7 +120,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.visualize.save as boolean; + return npStart.core.application.capabilities.visualize.save as boolean; } public getDisplayName() { @@ -122,14 +134,16 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); + const $injector = await chrome.dangerouslyGetActiveInjector(); const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); try { const visId = savedObject.id as string; - const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; + const editUrl = visId + ? npStart.core.http.basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) + : ''; const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { @@ -161,7 +175,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); + const $injector = await chrome.dangerouslyGetActiveInjector(); const savedVisualizations = $injector.get('savedVisualizations'); try { @@ -179,14 +193,16 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< // 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. if (this.visTypes) { - showNewVisModal(this.visTypes, { - editorParams: ['addToDashboard'], - }); + showNewVisModal( + this.visTypes, + { + editorParams: ['addToDashboard'], + }, + npStart.core.http.basePath.prepend, + npStart.core.uiSettings, + npStart.core.savedObjects + ); } return undefined; } } - -VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { - embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts new file mode 100644 index 00000000000000..71156bc38d4980 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.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 { State } from './legacy_imports'; +import { DataPublicPluginStart as DataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: DataStart +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = timefilter.getRefreshInterval(); + } + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} 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 d27003f39d4c08..9c00947d7663c9 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 @@ -18,10 +18,8 @@ */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; -const { docLinks } = getServices(); -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome, docLinks) { chrome.setHelpExtension({ appName: i18n.translate('kbn.visualize.helpMenu.appName', { defaultMessage: 'Visualize', diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js deleted file mode 100644 index 57707f63213768..00000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ /dev/null @@ -1,89 +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 { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -import './editor/editor'; -import { i18n } from '@kbn/i18n'; -import './saved_visualizations/_saved_vis'; -import './saved_visualizations/saved_visualizations'; -import visualizeListingTemplate from './listing/visualize_listing.html'; -import { VisualizeListingController } from './listing/visualize_listing'; -import { VisualizeConstants } from './visualize_constants'; -import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs'; - -import { getServices, FeatureCatalogueCategory } from './kibana_services'; - -const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); - -uiRoutes - .defaults(/visualize/, { - requireUICapability: 'visualize.show', - badge: uiCapabilities => { - if (uiCapabilities.visualize.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.visualize.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save visualizations', - }), - iconType: 'glasses' - }; - } - }) - .when(VisualizeConstants.LANDING_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getLandingBreadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) - }, - }) - .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getWizardStep1Breadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) - }, - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate( - 'kbn.visualize.visualizeDescription', - { - defaultMessage: 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - } - ), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts new file mode 100644 index 00000000000000..5e9f2fdeb89991 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/index.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 'ui/collapsible_sidebar'; // used in default editor +import 'ui/vis/editors/default/sidebar'; + +import { + IPrivate, + legacyChrome, + npSetup, + npStart, + SavedObjectRegistryProvider, + VisEditorTypesRegistryProvider, +} from './legacy_imports'; +import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; + +/** + * 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 legacyChrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const editorTypes = Private(VisEditorTypesRegistryProvider); + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + + return { + legacyChrome, + editorTypes, + savedObjectRegistry, + savedVisualizations: injector.get('savedVisualizations'), + }; +} + +(() => { + const instance = new VisualizePlugin(); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + embeddables, + navigation, + visualizations, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index e2201cdca9a576..36a9ecf3fcf8c9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -17,111 +17,59 @@ * 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 { + ChromeStart, + LegacyCoreStart, + SavedObjectsClientContract, + ToastsStart, + IUiSettingsClient, +} from 'kibana/public'; -import { npStart } from 'ui/new_platform'; -import angular from 'angular'; // just used in editor.js -import chromeLegacy from 'ui/chrome'; +import { NavigationStart } from '../../../navigation/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { DataPublicPluginStart, IndexPatternsContract } from '../../../../../plugins/data/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { SavedVisualizations } from './types'; -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 { timefilter } from 'ui/timefilter'; - -// Saved objects -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -// @ts-ignore -import { SavedObject, 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, - core: npStart.core, - - share: npStart.plugins.share, - data, - embeddables, - visualizations, - - // legacy - chromeLegacy, - docTitle, - FeatureCatalogueRegistryProvider, - FilterBarQueryFilterProvider, - getInjector: () => { - return chromeLegacy.dangerouslyGetActiveInjector(); - }, - SavedObjectProvider, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - timefilter, - uiModules, - uiRoutes, - wrapInI18nContext, +export interface VisualizeKibanaServices { + addBasePath: (url: string) => string; + chrome: ChromeStart; + core: LegacyCoreStart; + data: DataPublicPluginStart; + editorTypes: any; + embeddables: IEmbeddableStart; + getBasePath: () => string; + indexPatterns: IndexPatternsContract; + legacyChrome: any; + localStorage: Storage; + navigation: NavigationStart; + toastNotifications: ToastsStart; + savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; + savedQueryService: DataPublicPluginStart['query']['savedQueries']; + savedVisualizations: SavedVisualizations; + share: SharePluginStart; + uiSettings: IUiSettingsClient; + visualizeCapabilities: any; + visualizations: VisualizationsStart; +} - createUiStatsReporter, -}; +let services: VisualizeKibanaServices | null = null; +export function setServices(newServices: VisualizeKibanaServices) { + 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 visualize app?' + ); + } 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 { 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 { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { unhashUrl } from 'ui/state_management/state_hashing'; -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 const -export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; - -export { VisSavedObject } from './embeddable/visualize_embeddable'; +export function clearServices() { + services = null; +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js new file mode 100644 index 00000000000000..f47552e99a5c76 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js @@ -0,0 +1,169 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { find } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import editorTemplate from './editor/editor.html'; +import visualizeListingTemplate from './listing/visualize_listing.html'; + +import { initVisualizeAppDirective } from './visualize_app'; +import { VisualizeConstants } from './visualize_constants'; +import { VisualizeListingController } from './listing/visualize_listing'; +import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory } from './legacy_imports'; +import { syncOnMount } from './global_state_sync'; + +import { + getLandingBreadcrumbs, + getWizardStep1Breadcrumbs, + getCreateBreadcrumbs, + getEditBreadcrumbs +} from './breadcrumbs'; + +export function initVisualizeApp(app, deps) { + initVisualizeAppDirective(app, deps); + + app.run(globalState => { + syncOnMount(globalState, deps.data); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.data.query.timefilter.timefilter, + globalState, + $rootScope + ); + }); + + app.config(function ($routeProvider) { + const defaults = { + reloadOnSearch: false, + requireUICapability: 'visualize.show', + badge: () => { + if (deps.visualizeCapabilities.save) { + return undefined; + } + + return { + text: i18n.translate('kbn.visualize.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save visualizations', + }), + iconType: 'glasses', + }; + }, + }; + + $routeProvider + .when(VisualizeConstants.LANDING_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getLandingBreadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => false, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getWizardStep1Breadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => true, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.CREATE_PATH, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getCreateBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { core, data, savedVisualizations, visualizations } = deps; + const visTypes = visualizations.types.all(); + const visType = find(visTypes, { name: $route.current.params.type }); + const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; + const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId; + if (shouldHaveIndex && !hasIndex) { + throw new Error( + i18n.translate('kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage', { + defaultMessage: 'You must provide either an indexPattern or a savedSearchId', + }) + ); + } + + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis) + .catch(() => savedVis); + } + return savedVis; + }) + .catch(redirectWhenMissing({ + '*': '/visualize' + })); + } + } + }) + .when(`${VisualizeConstants.EDIT_PATH}/:id`, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { chrome, core, data, savedVisualizations } = deps; + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) + .then(() => savedVisualizations.get($route.current.params.id)) + .then(savedVis => { + chrome.recentlyAccessed.add( + savedVis.getFullPath(), + savedVis.title, + savedVis.id + ); + return savedVis; + }) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis).catch(() => savedVis); + } + return savedVis; + }) + .catch( + redirectWhenMissing({ + 'visualization': '/visualize', + 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id + }) + ); + } + } + }) + .when(`visualize/:tail*?`, { + redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + }); + }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts new file mode 100644 index 00000000000000..b9909e522b571f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.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. + */ + +/** + * The imports in this file are static functions and types which still live in legacy folders and are used + * within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually + * this list should become empty. Imports from the top level of shimmed or moved plugins can be imported + * directly where they are needed. + */ + +import chrome from 'ui/chrome'; + +export const legacyChrome = chrome; + +// @ts-ignore +export { AppState, AppStateProvider } from 'ui/state_management/app_state'; +export { State } from 'ui/state_management/state'; +// @ts-ignore +export { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { PersistedState } from 'ui/persisted_state'; + +export { npSetup, npStart } from 'ui/new_platform'; +export { IPrivate } from 'ui/private'; +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; + +export { SavedObjectRegistryProvider } from 'ui/saved_objects'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; + +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +// @ts-ignore +export { EventsProvider } from 'ui/events'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +export { confirmModalFactory } from 'ui/modals/confirm_modal'; +export { configureAppAngularModule, ensureDefaultIndexPattern } from 'ui/legacy_compat'; +export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; +// @ts-ignore +export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; + +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; +export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; + +// @ts-ignore +export { defaultEditor } from 'ui/vis/editors/default/default'; +export { VisType } from 'ui/vis'; +export { wrapInI18nContext } from 'ui/i18n'; + +export { VisSavedObject } from './embeddable/visualize_embeddable'; 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 deleted file mode 100644 index 34b662838880e3..00000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.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 React from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { - KuiEmptyTablePrompt, - KuiEmptyTablePromptPanel, - KuiButton, - KuiButtonIcon, -} from '@kbn/ui-framework/components'; - -function NoVisualizationsPrompt({ onCreateVis }) { - return ( - - } - > - - - } - message={i18n.translate('kbn.visualize.listing.noVisualizationsText', { - defaultMessage: `Looks like you don't have any visualizations. Let's create some!`, - })} - /> - - ); -} - -export { NoVisualizationsPrompt }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html index edb7cccbd46a2f..4ee8809fab2285 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -14,6 +14,9 @@ is-open="listingController.showNewVisModal" on-close="listingController.closeNewVisModal" vis-types-registry="listingController.visTypeRegistry" + add-base-path="listingController.addBasePath" + ui-settings="listingController.uiSettings" + saved-objects="listingController.savedObjects" >
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 f9e3a1a90115a3..b1ed5ce81d6eea 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 @@ -24,36 +24,48 @@ import { VisualizeConstants } from '../visualize_constants'; 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)) -); -app.directive('newVisModal', reactDirective => reactDirective(wrapInI18nContext(NewVisModal))); +import { wrapInI18nContext } from '../legacy_imports'; + +export function initListingDirective(app) { + app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable))); + app.directive('newVisModal', reactDirective => + reactDirective(wrapInI18nContext(NewVisModal), [ + ['visTypesRegistry', { watchDepth: 'collection' }], + ['onClose', { watchDepth: 'reference' }], + ['addBasePath', { watchDepth: 'reference' }], + ['uiSettings', { watchDepth: 'reference' }], + ['savedObjects', { watchDepth: 'reference' }], + 'isOpen', + ]) + ); +} export function VisualizeListingController($injector, createNewVis) { - const Private = $injector.get('Private'); - const config = $injector.get('config'); + const { + addBasePath, + chrome, + legacyChrome, + savedObjectRegistry, + savedObjectsClient, + data: { + query: { + timefilter: { timefilter }, + }, + }, + toastNotifications, + uiSettings, + visualizations, + core: { docLinks, savedObjects }, + } = getServices(); const kbnUrl = $injector.get('kbnUrl'); - const savedObjectClient = Private(SavedObjectsClientProvider); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); this.showNewVisModal = false; + this.addBasePath = addBasePath; + this.uiSettings = uiSettings; + this.savedObjects = savedObjects; this.createNewVis = () => { this.showNewVisModal = true; @@ -82,14 +94,14 @@ export function VisualizeListingController($injector, createNewVis) { } // TODO: Extract this into an external service. - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; + const services = savedObjectRegistry.byLoaderPropertiesName; const visualizationService = services.visualizations; this.visTypeRegistry = visualizations.types; this.fetchItems = filter => { - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = uiSettings.get('visualize:enableLabs'); return visualizationService - .findListItems(filter, config.get('savedObjects:listingLimit')) + .findListItems(filter, uiSettings.get('savedObjects:listingLimit')) .then(result => { this.totalItems = result.total; @@ -103,11 +115,11 @@ export function VisualizeListingController($injector, createNewVis) { this.deleteSelectedItems = function deleteSelectedItems(selectedItems) { return Promise.all( selectedItems.map(item => { - return savedObjectClient.delete(item.savedObjectType, item.id); + return savedObjectsClient.delete(item.savedObjectType, item.id); }) ) .then(() => { - chromeLegacy.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + legacyChrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); }) .catch(error => { toastNotifications.addError(error, { @@ -126,7 +138,7 @@ export function VisualizeListingController($injector, createNewVis) { }, ]); - this.listingLimit = config.get('savedObjects:listingLimit'); + this.listingLimit = uiSettings.get('savedObjects:listingLimit'); - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); } 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 efab03303aa809..890fa64af96935 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 @@ -27,22 +27,21 @@ import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elas import { getServices } from '../kibana_services'; -const { capabilities, toastNotifications, uiSettings } = getServices(); - class VisualizeListingTable extends Component { constructor(props) { super(props); } render() { + const { visualizeCapabilities, uiSettings, toastNotifications } = getServices(); return ( item.canDelete} diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts new file mode 100644 index 00000000000000..1aa2d70dabea6e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -0,0 +1,161 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 { + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; + +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { DataPublicPluginStart } from '../../../../../plugins/data/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; +import { VisualizeConstants } from './visualize_constants'; +import { setServices, VisualizeKibanaServices } from './kibana_services'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../plugins/home/public'; +import { defaultEditor, VisEditorTypesRegistryProvider } from './legacy_imports'; +import { SavedVisualizations } from './types'; + +export interface LegacyAngularInjectedDependencies { + legacyChrome: any; + editorTypes: any; + savedObjectRegistry: any; + savedVisualizations: SavedVisualizations; +} + +export interface VisualizePluginStartDependencies { + data: DataPublicPluginStart; + embeddables: IEmbeddableStart; + navigation: NavigationStart; + share: SharePluginStart; + visualizations: VisualizationsStart; +} + +export interface VisualizePluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + }; + home: HomePublicPluginSetup; + kibana_legacy: KibanaLegacySetup; +} + +export class VisualizePlugin implements Plugin { + private startDependencies: { + data: DataPublicPluginStart; + embeddables: IEmbeddableStart; + navigation: NavigationStart; + savedObjectsClient: SavedObjectsClientContract; + share: SharePluginStart; + visualizations: VisualizationsStart; + } | null = null; + + public async setup( + core: CoreSetup, + { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: VisualizePluginSetupDependencies + ) { + kibana_legacy.registerLegacyApp({ + id: 'visualize', + title: 'Visualize', + mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + + const { + savedObjectsClient, + embeddables, + navigation, + visualizations, + data, + share, + } = this.startDependencies; + + const angularDependencies = await getAngularDependencies(); + const deps: VisualizeKibanaServices = { + ...angularDependencies, + addBasePath: contextCore.http.basePath.prepend, + core: contextCore as LegacyCoreStart, + chrome: contextCore.chrome, + data, + embeddables, + getBasePath: core.http.basePath.get, + indexPatterns: data.indexPatterns, + localStorage: new Storage(localStorage), + navigation, + savedObjectsClient, + savedQueryService: data.query.savedQueries, + share, + toastNotifications: contextCore.notifications.toasts, + uiSettings: contextCore.uiSettings, + visualizeCapabilities: contextCore.application.capabilities.visualize, + visualizations, + }; + setServices(deps); + + const { renderApp } = await import('./application'); + return renderApp(params.element, params.appBasePath, deps); + }, + }); + + home.featureCatalogue.register({ + id: 'visualize', + title: 'Visualize', + description: i18n.translate('kbn.visualize.visualizeDescription', { + defaultMessage: + 'Create visualizations and aggregate data stores in your Elasticsearch indices.', + }), + icon: 'visualizeApp', + path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); + + VisEditorTypesRegistryProvider.register(defaultEditor); + } + + public start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { embeddables, navigation, data, share, visualizations }: VisualizePluginStartDependencies + ) { + this.startDependencies = { + data, + embeddables, + navigation, + savedObjectsClient, + share, + visualizations, + }; + + const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); + embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + } +} 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 c83f7f5a5da8b0..b6a39812153846 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 './kibana_services'; +import { VisSavedObject } from './legacy_imports'; export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts new file mode 100644 index 00000000000000..c64287a0e63b82 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.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 { IModule } from 'angular'; +import { VisualizeKibanaServices } from './kibana_services'; + +// @ts-ignore +import { initEditorDirective } from './editor/editor'; +// @ts-ignore +import { initListingDirective } from './listing/visualize_listing'; + +export function initVisualizeAppDirective(app: IModule, deps: VisualizeKibanaServices) { + initEditorDirective(app, deps); + initListingDirective(app); +} 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 4aa614b68ea232..04b7cddc752891 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 @@ -2,6 +2,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`] = ` @@ -1287,6 +1308,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` exports[`NewVisModal should render as expected 1`] = ` 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 99d9590e750fd0..4eafd06c7bb20c 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,33 +20,18 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { NewVisModal } from './new_vis_modal'; -import { VisType } from '../kibana_services'; +import { VisType } from '../legacy_imports'; 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'; +jest.mock('../legacy_imports', () => ({ + State: () => null, + AppState: () => null, +})); -beforeEach(() => { - jest.clearAllMocks(); -}); +import { NewVisModal } from './new_vis_modal'; +import { SavedObjectsStart } from 'kibana/public'; describe('NewVisModal', () => { - const settingsGet = getServices().uiSettings.get as jest.Mock; - const defaultVisTypeParams = { hidden: false, visualization: class Controller { @@ -76,17 +61,38 @@ describe('NewVisModal', () => { }, getAliases: () => [], }; + const addBasePath = (url: string) => `testbasepath${url}`; + const settingsGet = jest.fn(); + const uiSettings: any = { get: settingsGet }; + + beforeEach(() => { + jest.clearAllMocks(); + }); it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} + /> ); expect(wrapper).toMatchSnapshot(); }); it('should show a button for regular visualizations', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} + /> ); expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); }); @@ -95,7 +101,14 @@ describe('NewVisModal', () => { it('should open the editor for visualizations without search', () => { window.location.assign = jest.fn(); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} + /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); visButton.simulate('click'); @@ -110,6 +123,9 @@ describe('NewVisModal', () => { onClose={() => null} visTypesRegistry={visTypes} editorParams={['foo=true', 'bar=42']} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -121,7 +137,14 @@ describe('NewVisModal', () => { describe('filter for visualization types', () => { it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} + /> ); const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); searchBox.simulate('change', { target: { value: 'with' } }); @@ -133,7 +156,14 @@ describe('NewVisModal', () => { it('should not show experimental visualizations if visualize:enableLabs is false', () => { settingsGet.mockReturnValue(false); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); }); @@ -141,7 +171,14 @@ describe('NewVisModal', () => { it('should show experimental visualizations if visualize:enableLabs is true', () => { settingsGet.mockReturnValue(true); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); }); 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 420f0e5198056c..0402265610fb12 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,20 +22,22 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; +import { VisType } from '../legacy_imports'; 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; visTypesRegistry: TypesStart; editorParams?: string[]; + addBasePath: (path: string) => string; + uiSettings: IUiSettingsClient; + savedObjects: SavedObjectsStart; } interface TypeSelectionState { @@ -55,7 +57,7 @@ class NewVisModal extends React.Component - + ) : ( ); @@ -124,7 +132,7 @@ class NewVisModal extends React.Component void; visType: VisType; + uiSettings: IUiSettingsClient; + savedObjects: SavedObjectsStart; } export class SearchSelection extends React.Component { @@ -52,7 +54,7 @@ export class SearchSelection extends React.Component { - { }, ]} fixedPageSize={this.fixedPageSize} + uiSettings={this.props.uiSettings} + savedObjects={this.props.savedObjects} /> diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index fa2ca6747bc408..88838e16c40e24 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -20,7 +20,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -30,7 +31,10 @@ interface ShowNewVisModalParams { export function showNewVisModal( visTypeRegistry: TypesStart, - { editorParams = [] }: ShowNewVisModalParams = {} + { editorParams = [] }: ShowNewVisModalParams = {}, + addBasePath: (path: string) => string, + uiSettings: IUiSettingsClient, + savedObjects: SavedObjectsStart ) { const container = document.createElement('div'); const onClose = () => { @@ -40,14 +44,17 @@ export function showNewVisModal( document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } 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 382f475669f5dc..3093499a030c8d 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 @@ -21,18 +21,6 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; -jest.mock('../../kibana_services', () => { - return { - getServices: () => ({ - addBasePath: jest.fn((url: string) => `testbasepath${url}`), - }), - }; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - describe('NewVisHelp', () => { it('should render as expected', () => { expect( @@ -53,6 +41,7 @@ describe('NewVisHelp', () => { stage: 'production', }, ]} + addBasePath={(url: string) => `testbasepath${url}`} /> ) ).toMatchInlineSnapshot(` 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 44c36f7d17d550..107cbc0e754b51 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 @@ -22,10 +22,9 @@ import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; import { VisTypeAliasListEntry } from './type_selection'; -import { getServices } from '../../kibana_services'; - interface Props { promotedTypes: VisTypeAliasListEntry[]; + addBasePath: (path: string) => string; } export function NewVisHelp(props: Props) { @@ -43,7 +42,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

string; onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; visTypesRegistry: TypesStart; showExperimental: boolean; @@ -153,6 +154,7 @@ class TypeSelection extends React.Component t.promotion)} + addBasePath={this.props.addBasePath} /> )} diff --git a/src/legacy/core_plugins/kibana/server/routes/api/scroll_search/index.js b/src/legacy/core_plugins/kibana/server/routes/api/scroll_search/index.js deleted file mode 100644 index a3efd890f78b89..00000000000000 --- a/src/legacy/core_plugins/kibana/server/routes/api/scroll_search/index.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. - */ - -export function scrollSearchApi(server) { - server.route({ - path: '/api/kibana/legacy_scroll_start', - method: ['POST'], - handler: async (req) => { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - const { index, size, body } = req.payload; - const params = { - index, - size, - body, - scroll: '1m', - sort: '_doc', - }; - - try { - return await callWithRequest(req, 'search', params); - } catch (err) { - throw server.plugins.elasticsearch.handleESError(err); - } - } - }); - - server.route({ - path: '/api/kibana/legacy_scroll_continue', - method: ['POST'], - handler: async (req) => { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - const { scrollId } = req.payload; - try { - return await callWithRequest(req, 'scroll', { scrollId, scroll: '1m' }); - } catch (err) { - throw server.plugins.elasticsearch.handleESError(err); - } - } - }); -} diff --git a/src/legacy/core_plugins/kibana/server/routes/api/search/count/register_count.js b/src/legacy/core_plugins/kibana/server/routes/api/search/count/register_count.js deleted file mode 100644 index 87b97ca2e1437c..00000000000000 --- a/src/legacy/core_plugins/kibana/server/routes/api/search/count/register_count.js +++ /dev/null @@ -1,42 +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'; - -export default function registerCount(server) { - server.route({ - path: '/api/kibana/{id}/_count', - method: ['POST', 'GET'], - handler: async function (req) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - const boundCallWithRequest = _.partial(callWithRequest, req); - - try { - const res = await boundCallWithRequest('count', { - allowNoIndices: false, - index: req.params.id - }); - - return { count: res.count }; - } catch (err) { - throw server.plugins.elasticsearch.handleESError(err); - } - } - }); -} diff --git a/src/legacy/core_plugins/kibana/server/routes/api/search/index.js b/src/legacy/core_plugins/kibana/server/routes/api/search/index.js deleted file mode 100644 index 32174f1f28e520..00000000000000 --- a/src/legacy/core_plugins/kibana/server/routes/api/search/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 registerCount from './count/register_count'; - -export function searchApi(server) { - registerCount(server); -} 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 4ffe790bd51a18..1f5ed443fee80e 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 @@ -18,6 +18,7 @@ */ import { get, map } from 'lodash'; +import { abortableRequestHandler } from '../../../../../elasticsearch/lib/abortable_request_handler'; export function registerValueSuggestions(server) { const serverConfig = server.config(); @@ -26,7 +27,7 @@ export function registerValueSuggestions(server) { server.route({ path: '/api/kibana/suggestions/values/{index}', method: ['POST'], - handler: async function (req) { + handler: abortableRequestHandler(async function (signal, req) { const { index } = req.params; const { field: fieldName, query, boolFilter } = req.payload; const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); @@ -46,7 +47,7 @@ export function registerValueSuggestions(server) { ); try { - const response = await callWithRequest(req, 'search', { index, body }); + const response = await callWithRequest(req, 'search', { index, body }, { signal }); const buckets = get(response, 'aggregations.suggestions.buckets') || get(response, 'aggregations.nestedSuggestions.suggestions.buckets') || []; @@ -55,7 +56,7 @@ export function registerValueSuggestions(server) { } catch (error) { throw server.plugins.elasticsearch.handleESError(error); } - }, + }), }); } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/activemq_logs/index.js b/src/legacy/core_plugins/kibana/server/tutorials/activemq_logs/index.js new file mode 100644 index 00000000000000..d406147384138c --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/activemq_logs/index.js @@ -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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/filebeat_instructions'; + +export function activemqLogsSpecProvider(server, context) { + const moduleName = 'activemq'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + return { + id: 'activemqLogs', + name: i18n.translate('kbn.server.tutorials.activemqLogs.nameTitle', { + defaultMessage: 'ActiveMQ logs', + }), + category: TUTORIAL_CATEGORY.LOGGING, + shortDescription: i18n.translate('kbn.server.tutorials.activemqLogs.shortDescription', { + defaultMessage: 'Collect ActiveMQ logs with Filebeat.', + }), + longDescription: i18n.translate('kbn.server.tutorials.activemqLogs.longDescription', { + defaultMessage: 'Collect ActiveMQ logs with Filebeat. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-activemq.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/activemq.svg', + artifacts: { + dashboards: [ + { + id: '26434790-1464-11ea-8fd8-030a13064883', + linkLabel: i18n.translate('kbn.server.tutorials.activemqLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'ActiveMQ Application Events', + }), + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-activemq.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/activemq_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js b/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js index 8a7472d318701e..64698a54eed3c5 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js @@ -17,6 +17,7 @@ * under the License. */ +import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from '../../../../common/tutorials/instruction_variant'; @@ -32,27 +33,27 @@ import { createDotNetAgentInstructions, } from '../instructions/apm_agent_instructions'; -function getIfExists(config, key) { - return config.has(key) && config.get(key); +function getIfExists(obj, key) { + return has(obj, key) && get(obj, key); } -export function createElasticCloudInstructions(config) { - const apmServerUrl = getIfExists(config, 'xpack.cloud.apm.url'); +export function createElasticCloudInstructions(cloudSetup) { + const apmServerUrl = getIfExists(cloudSetup, 'apm.url'); const instructionSets = []; if (!apmServerUrl) { - instructionSets.push(getApmServerInstructionSet(config)); + instructionSets.push(getApmServerInstructionSet(cloudSetup)); } - instructionSets.push(getApmAgentInstructionSet(config)); + instructionSets.push(getApmAgentInstructionSet(cloudSetup)); return { instructionSets, }; } -function getApmServerInstructionSet(config) { - const cloudId = getIfExists(config, 'xpack.cloud.id'); +function getApmServerInstructionSet(cloudSetup) { + const cloudId = getIfExists(cloudSetup, 'cloudId'); return { title: i18n.translate('kbn.server.tutorials.apm.apmServer.title', { defaultMessage: 'APM Server', @@ -75,9 +76,9 @@ function getApmServerInstructionSet(config) { }; } -function getApmAgentInstructionSet(config) { - const apmServerUrl = getIfExists(config, 'xpack.cloud.apm.url'); - const secretToken = getIfExists(config, 'xpack.cloud.apm.secret_token'); +function getApmAgentInstructionSet(cloudSetup) { + const apmServerUrl = getIfExists(cloudSetup, 'apm.url'); + const secretToken = getIfExists(cloudSetup, 'apm.secretToken'); return { title: i18n.translate('kbn.server.tutorials.apm.elasticCloudInstructions.title', { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js index faad7822ff9a1e..d86810a8bdf185 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js @@ -39,6 +39,7 @@ function isEnabled(config) { export function apmSpecProvider(server) { const config = server.config(); const apmIndexPatternTitle = config.get('apm_oss.indexPattern'); + const { cloud } = server.newPlatform.setup; const savedObjects = [ { @@ -95,7 +96,7 @@ It allows you to monitor the performance of thousands of applications in real ti euiIconType: 'logoAPM', artifacts, onPrem: onPremInstructions(config), - elasticCloud: createElasticCloudInstructions(config), + elasticCloud: createElasticCloudInstructions(cloud), previewImagePath: '/plugins/kibana/home/tutorial_resources/apm/apm.png', savedObjects, savedObjectsInstallMsg: i18n.translate( diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json index 69a165c09c2f91..9001613623ccb8 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/azure_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/azure_metrics/index.js new file mode 100644 index 00000000000000..8bd0d17b143cf0 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/azure_metrics/index.js @@ -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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function azureMetricsSpecProvider(context) { + const moduleName = 'azure'; + return { + id: 'azureMetrics', + name: i18n.translate('kbn.server.tutorials.azureMetrics.nameTitle', { + defaultMessage: 'Azure metrics', + }), + isBeta: true, + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.azureMetrics.shortDescription', { + defaultMessage: 'Fetch Azure Monitor metrics.', + }), + longDescription: i18n.translate('kbn.server.tutorials.azureMetrics.longDescription', { + defaultMessage: 'The `azure` Metricbeat module fetches Azure Monitor metrics. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-azure.html', + }, + }), + euiIconType: 'logoAzure', + artifacts: { + application: { + label: i18n.translate('kbn.server.tutorials.azureMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/kibana#/discover' + }, + dashboards: [], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-azure.html' + } + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index f36909e59f39be..ec6ffe7e6a7bac 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -80,7 +80,9 @@ import { consulMetricsSpecProvider } from './consul_metrics'; import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; import { traefikMetricsSpecProvider } from './traefik_metrics'; import { awsLogsSpecProvider } from './aws_logs'; +import { activemqLogsSpecProvider } from './activemq_logs'; import { activemqMetricsSpecProvider } from './activemq_metrics'; +import { azureMetricsSpecProvider } from './azure_metrics'; export function registerTutorials(server) { server.newPlatform.setup.plugins.home.tutorials.registerTutorial(systemLogsSpecProvider); @@ -147,5 +149,7 @@ export function registerTutorials(server) { server.newPlatform.setup.plugins.home.tutorials.registerTutorial(cockroachdbMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(traefikMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(awsLogsSpecProvider); + server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqLogsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqMetricsSpecProvider); + server.newPlatform.setup.plugins.home.tutorials.registerTutorial(azureMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/management/index.ts b/src/legacy/core_plugins/management/index.ts new file mode 100644 index 00000000000000..65601b53718151 --- /dev/null +++ b/src/legacy/core_plugins/management/index.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import { Legacy } from '../../../../kibana'; + +// eslint-disable-next-line import/no-default-export +export default function ManagementPlugin(kibana: any) { + const config: Legacy.PluginSpecOptions = { + id: 'management', + publicDir: resolve(__dirname, 'public'), + config: (Joi: any) => { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + init: (server: Legacy.Server) => ({}), + }; + + return new kibana.Plugin(config); +} diff --git a/src/legacy/core_plugins/management/package.json b/src/legacy/core_plugins/management/package.json new file mode 100644 index 00000000000000..77d33a7bce3b6c --- /dev/null +++ b/src/legacy/core_plugins/management/package.json @@ -0,0 +1,5 @@ +{ + "name": "management", + "version": "kibana" +} + \ No newline at end of file diff --git a/src/legacy/core_plugins/management/public/index.ts b/src/legacy/core_plugins/management/public/index.ts new file mode 100644 index 00000000000000..3d64b6d2aa2bb1 --- /dev/null +++ b/src/legacy/core_plugins/management/public/index.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. + */ + +/** + * Static np-ready code, re-exported here so consumers can import from + * `src/legacy/core_plugins/management/public` + * + * @public + */ + +export { + ManagementSetup, + ManagementStart, + plugin, + IndexPatternCreationConfig, + IndexPatternListConfig, + SavedObjectsManagementAction, + SavedObjectsManagementRecord, +} from './np_ready'; + +export { + processImportResponse, + ProcessedImportResponse, +} from '../../kibana/public/management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts new file mode 100644 index 00000000000000..7c17f0c6bddc01 --- /dev/null +++ b/src/legacy/core_plugins/management/public/legacy.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. + */ + +/** + * New Platform Shim + * + * In this file, we import any legacy dependencies we have, and shim them into + * our plugin by manually constructing the values that the new platform will + * eventually be passing to the `setup/start` method of our plugin definition. + * + * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy + * world code. Then when it comes time to migrate to the new platform, we can + * simply delete this shim file. + * + * We are also calling `setup/start` here and exporting our public contract so that + * other legacy plugins are able to import from '../core_plugins/visualizations/legacy' + * and receive the response value of the `setup/start` contract, mimicking the + * data that will eventually be injected by the new platform. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { plugin } from '.'; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, {}); +export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/core_plugins/management/public/np_ready/index.ts b/src/legacy/core_plugins/management/public/np_ready/index.ts new file mode 100644 index 00000000000000..ec93516df8349d --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/index.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. + */ + +/** + * Management Plugin - public + * + * This is the entry point for the entire client-side public contract of the plugin. + * If something is not explicitly exported here, you can safely assume it is private + * to the plugin and not considered stable. + * + * All stateful contracts will be injected by the platform at runtime, and are defined + * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are + * either types, or static code. + */ +import { PluginInitializerContext } from 'src/core/public'; +import { ManagementPlugin } from './plugin'; +export { ManagementSetup, ManagementStart } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ManagementPlugin(initializerContext); +} + +export { + IndexPatternCreationConfig, + IndexPatternListConfig, +} from './services/index_pattern_management'; + +export { + SavedObjectsManagementAction, + SavedObjectsManagementRecord, +} from './services/saved_objects_management'; diff --git a/src/legacy/core_plugins/management/public/np_ready/mocks.ts b/src/legacy/core_plugins/management/public/np_ready/mocks.ts new file mode 100644 index 00000000000000..13a0cf4c891a39 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/mocks.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 { PluginInitializerContext } from 'src/core/public'; +import { coreMock } from '../../../../../core/public/mocks'; +import { ManagementSetup, ManagementStart, ManagementPlugin } from './plugin'; + +const createSetupContract = (): ManagementSetup => ({ + indexPattern: { + creation: { + add: jest.fn(), + getType: jest.fn(), + getIndexPatternCreationOptions: jest.fn(), + } as any, + list: { + add: jest.fn(), + getIndexPatternTags: jest.fn(), + getFieldInfo: jest.fn(), + areScriptedFieldsEnabled: jest.fn(), + } as any, + }, + savedObjects: { + registry: { + register: jest.fn(), + has: jest.fn(), + get: jest.fn(() => []), + }, + }, +}); + +const createStartContract = (): ManagementStart => ({}); + +const createInstance = async () => { + const plugin = new ManagementPlugin({} as PluginInitializerContext); + + const setup = plugin.setup(coreMock.createSetup(), {}); + const doStart = () => plugin.start(coreMock.createStart(), {}); + + return { + plugin, + setup, + doStart, + }; +}; + +export const mockManagementPlugin = { + createSetupContract, + createStartContract, + createInstance, +}; diff --git a/src/legacy/core_plugins/management/public/np_ready/plugin.ts b/src/legacy/core_plugins/management/public/np_ready/plugin.ts new file mode 100644 index 00000000000000..032a46439ba55a --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/plugin.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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { IndexPatternManagementService, IndexPatternManagementSetup } from './services'; +import { + SavedObjectsManagementService, + SavedObjectsManagementServiceSetup, +} from './services/saved_objects_management'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ManagementPluginSetupDependencies {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ManagementPluginStartDependencies {} + +export interface ManagementSetup { + indexPattern: IndexPatternManagementSetup; + savedObjects: SavedObjectsManagementServiceSetup; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ManagementStart {} + +export class ManagementPlugin + implements + Plugin< + ManagementSetup, + ManagementStart, + ManagementPluginSetupDependencies, + ManagementPluginStartDependencies + > { + private readonly indexPattern = new IndexPatternManagementService(); + private readonly savedObjects = new SavedObjectsManagementService(); + + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, deps: ManagementPluginSetupDependencies) { + return { + indexPattern: this.indexPattern.setup({ httpClient: core.http }), + savedObjects: this.savedObjects.setup(), + }; + } + + public start(core: CoreStart, plugins: ManagementPluginStartDependencies) { + return {}; + } + + public stop() { + this.indexPattern.stop(); + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index.ts new file mode 100644 index 00000000000000..4d55fce3d8a7dc --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/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_pattern_management'; +export * from './saved_objects_management'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts new file mode 100644 index 00000000000000..0598c88c80ba7c --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts @@ -0,0 +1,124 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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'; + +const indexPatternTypeName = i18n.translate( + 'management.editIndexPattern.createIndex.defaultTypeName', + { defaultMessage: 'index pattern' } +); + +const indexPatternButtonText = i18n.translate( + 'management.editIndexPattern.createIndex.defaultButtonText', + { defaultMessage: 'Standard index pattern' } +); + +const indexPatternButtonDescription = i18n.translate( + 'management.editIndexPattern.createIndex.defaultButtonDescription', + { defaultMessage: 'Perform full aggregations against any data' } +); + +export type UrlHandler = (url: string) => void; + +export interface IndexPatternCreationOption { + text: string; + description: string; + testSubj: string; + onClick: () => void; + isBeta?: boolean; +} + +export class IndexPatternCreationConfig { + public readonly key = 'default'; + + protected type?: string; + protected name: string; + protected showSystemIndices: boolean; + protected httpClient: object | null; + protected isBeta: boolean; + + constructor({ + type = undefined, + name = indexPatternTypeName, + showSystemIndices = true, + httpClient = null, + isBeta = false, + }: { + type?: string; + name?: string; + showSystemIndices?: boolean; + httpClient?: object | null; + isBeta?: boolean; + }) { + this.type = type; + this.name = name; + this.showSystemIndices = showSystemIndices; + this.httpClient = httpClient; + this.isBeta = isBeta; + } + + public async getIndexPatternCreationOption( + urlHandler: UrlHandler + ): Promise { + return { + text: indexPatternButtonText, + description: indexPatternButtonDescription, + testSubj: `createStandardIndexPatternButton`, + onClick: () => { + urlHandler('/management/kibana/index_pattern'); + }, + }; + } + + public getIndexPatternType() { + return this.type; + } + + public getIndexPatternName() { + return this.name; + } + + public getIsBeta() { + return this.isBeta; + } + + public getShowSystemIndices() { + return this.showSystemIndices; + } + + public getIndexTags() { + return []; + } + + public checkIndicesForErrors() { + return undefined; + } + + public getIndexPatternMappings() { + return {}; + } + + public renderPrompt() { + return null; + } + + public getFetchForWildcardOptions() { + return {}; + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts new file mode 100644 index 00000000000000..84c4c28aa2260f --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/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 { IndexPatternCreationConfig } from './config'; +export { IndexPatternCreationManager } from './manager'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts new file mode 100644 index 00000000000000..605ffdd6f21349 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.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 { HttpServiceBase } from '../../../../../../../../core/public'; +import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; + +export class IndexPatternCreationManager { + private configs: IndexPatternCreationConfig[]; + + constructor(private readonly httpClient: HttpServiceBase) { + this.configs = []; + } + + public add(Config: typeof IndexPatternCreationConfig) { + const config = new Config({ httpClient: this.httpClient }); + if (this.configs.findIndex(c => c.key === config.key) !== -1) { + throw new Error(`${config.key} exists in IndexPatternCreationManager.`); + } + this.configs.push(config); + } + + public getType(key: string | undefined): IndexPatternCreationConfig | null { + if (key) { + const index = this.configs.findIndex(config => config.key === key); + return this.configs[index] || null; + } else { + return this.getType('default'); + } + } + + public async getIndexPatternCreationOptions(urlHandler: UrlHandler) { + const options: IndexPatternCreationOption[] = []; + await Promise.all( + this.configs.map(async config => { + const option = config.getIndexPatternCreationOption + ? await config.getIndexPatternCreationOption(urlHandler) + : null; + if (option) { + options.push(option); + } + }) + ); + return options; + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts new file mode 100644 index 00000000000000..2abe13eb0e2927 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/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 './index_pattern_management_service'; +export { IndexPatternCreationConfig } from './creation'; +export { IndexPatternListConfig } from './list'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts new file mode 100644 index 00000000000000..b9e07564a324cb --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.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 { HttpServiceBase } from '../../../../../../../core/public'; +import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; +import { IndexPatternListManager, IndexPatternListConfig } from './list'; + +interface SetupDependencies { + httpClient: HttpServiceBase; +} + +/** + * Index patterns management service + * + * @internal + */ +export class IndexPatternManagementService { + public setup({ httpClient }: SetupDependencies) { + const creation = new IndexPatternCreationManager(httpClient); + const list = new IndexPatternListManager(); + + creation.add(IndexPatternCreationConfig); + list.add(IndexPatternListConfig); + + return { + creation, + list, + }; + } + + public stop() { + // nothing to do here yet. + } +} + +/** @internal */ +export type IndexPatternManagementSetup = ReturnType; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts new file mode 100644 index 00000000000000..dd4d77a6811717 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { IIndexPattern, IFieldType } from 'src/plugins/data/public'; + +export interface IndexPatternTag { + key: string; + name: string; +} + +export class IndexPatternListConfig { + public readonly key = 'default'; + + public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean): IndexPatternTag[] { + return isDefault + ? [ + { + key: 'default', + name: i18n.translate('management.editIndexPattern.list.defaultIndexPatternListName', { + defaultMessage: 'Default', + }), + }, + ] + : []; + } + + public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + return []; + } + + public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + return true; + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts new file mode 100644 index 00000000000000..114226b3a45708 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/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 { IndexPatternListConfig } from './config'; +export { IndexPatternListManager } from './manager'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts new file mode 100644 index 00000000000000..73ca33ae914a9b --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.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 { IIndexPattern, IFieldType } from 'src/plugins/data/public'; +import { IndexPatternListConfig, IndexPatternTag } from './config'; + +export class IndexPatternListManager { + private configs: IndexPatternListConfig[]; + + constructor() { + this.configs = []; + } + + public add(Config: typeof IndexPatternListConfig) { + const config = new Config(); + if (this.configs.findIndex(c => c.key === config.key) !== -1) { + throw new Error(`${config.key} exists in IndexPatternListManager.`); + } + this.configs.push(config); + } + + public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { + return this.configs.reduce((tags: IndexPatternTag[], config) => { + return config.getIndexPatternTags + ? tags.concat(config.getIndexPatternTags(indexPattern, isDefault)) + : tags; + }, []); + } + + public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + return this.configs.reduce((info: string[], config) => { + return config.getFieldInfo ? info.concat(config.getFieldInfo(indexPattern, field)) : info; + }, []); + } + + public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + return this.configs.every(config => { + return config.areScriptedFieldsEnabled ? config.areScriptedFieldsEnabled(indexPattern) : true; + }); + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts new file mode 100644 index 00000000000000..bad3b3ac36ef74 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/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 './saved_objects_management_action_registry'; +export * from './saved_objects_management_action'; +export * from './saved_objects_management_service'; diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts similarity index 96% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts index a09f842e367133..d83afb195a492d 100644 --- a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ReactNode } from '@elastic/eui/node_modules/@types/react'; +import { ReactNode } from 'react'; export interface SavedObjectsManagementRecordReference { type: string; diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.test.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.test.ts similarity index 100% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.test.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.test.ts diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.ts similarity index 100% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts new file mode 100644 index 00000000000000..d5e90d12cccc91 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.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 { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; + +export class SavedObjectsManagementService { + public setup() { + return { + registry: SavedObjectsManagementActionRegistry, + }; + } + + public stop() {} +} + +/** @internal */ +export type SavedObjectsManagementServiceSetup = ReturnType; diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index a41d638986ae5d..aaf0a8a308aeae 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -21,7 +21,7 @@ import { CoreStart, Plugin, PluginInitializerContext, - UiSettingsClientContract, + IUiSettingsClient, } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; @@ -35,7 +35,7 @@ import { createRegionMapTypeDefinition } from './region_map_type'; /** @private */ interface RegionMapVisualizationDependencies extends LegacyDependenciesPluginSetup { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } /** @internal */ 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 f64237000ae416..7fbf715ac3616b 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 @@ -18,8 +18,9 @@ */ import chrome from 'ui/chrome'; -import { hashUrl } from 'ui/state_management/state_hashing'; +import { hashUrl } from '../../../../plugins/kibana_utils/public'; import uiRoutes from 'ui/routes'; +import { fatalError } from 'ui/notify'; uiRoutes.enable(); uiRoutes @@ -27,11 +28,14 @@ uiRoutes resolve: { url: function (AppState, globalState, $window) { const redirectUrl = chrome.getInjected('redirectUrl'); + try { + const hashedUrl = hashUrl(redirectUrl); + const url = chrome.addBasePath(hashedUrl); - const hashedUrl = hashUrl([new AppState(), globalState], redirectUrl); - const url = chrome.addBasePath(hashedUrl); - - $window.location = url; + $window.location = url; + } catch (e) { + fatalError(e); + } } } }); diff --git a/src/legacy/core_plugins/telemetry/index.ts b/src/legacy/core_plugins/telemetry/index.ts index 9f850fc0fe7192..35037d3793a95f 100644 --- a/src/legacy/core_plugins/telemetry/index.ts +++ b/src/legacy/core_plugins/telemetry/index.ts @@ -23,6 +23,8 @@ import JoiNamespace from 'joi'; import { Server } from 'hapi'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { i18n } from '@kbn/i18n'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getConfigPath } from '../../../core/server/path'; // @ts-ignore import mappings from './mappings.json'; import { CONFIG_TELEMETRY, getConfigTelemetryDesc } from './common/constants'; @@ -47,7 +49,7 @@ const telemetry = (kibana: any) => { otherwise: Joi.boolean().default(true), }), // `config` is used internally and not intended to be set - config: Joi.string().default(Joi.ref('$defaultConfigPath')), + config: Joi.string().default(getConfigPath()), banner: Joi.boolean().default(true), url: Joi.when('$dev', { is: true, diff --git a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap index a7f8d72e016f87..079a43e77616d2 100644 --- a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap @@ -38,7 +38,24 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1` "defVal": true, "description":

- Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic. + + + , + } + } + />

-

- - - -

, "type": "boolean", "value": false, diff --git a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index 6c6ace71af4d09..f6012a271cde5e 100644 --- a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js @@ -29,7 +29,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { PRIVACY_STATEMENT_URL } from '../../common/constants'; import { OptInExampleFlyout } from './opt_in_details_component'; import { Field } from 'ui/management'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -52,7 +52,7 @@ export class TelemetryForm extends Component { queryMatches: null, } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { query } = nextProps; @@ -162,7 +162,23 @@ export class TelemetryForm extends Component { renderDescription = () => ( -

{getConfigTelemetryDesc()}

+

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

-

- - - -

) diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts index 2b2e946198e0a8..cf6059faf0c056 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts @@ -75,7 +75,7 @@ describe('telemetry_usage_collector', () => { // empty writeFileSync(tempFiles.empty, ''); // 1 byte too big - writeFileSync(tempFiles.too_big, new Buffer(MAX_FILE_SIZE + 1)); + writeFileSync(tempFiles.too_big, Buffer.alloc(MAX_FILE_SIZE + 1)); // write-only file writeFileSync(tempFiles.unreadable, 'valid: true', { mode: 0o222 }); // valid diff --git a/src/legacy/core_plugins/tests_bundle/find_source_files.js b/src/legacy/core_plugins/tests_bundle/find_source_files.js index eb5b4f50e4e85b..f880f6ebc4553c 100644 --- a/src/legacy/core_plugins/tests_bundle/find_source_files.js +++ b/src/legacy/core_plugins/tests_bundle/find_source_files.js @@ -18,7 +18,8 @@ */ -import { fromRoot } from '../../../legacy/utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { fromRoot } from '../../../core/server/utils'; import { chain } from 'lodash'; import { resolve } from 'path'; import { fromNode } from 'bluebird'; diff --git a/src/legacy/core_plugins/tests_bundle/index.js b/src/legacy/core_plugins/tests_bundle/index.js index 803fe1d220f44f..4c7ad4c8ea0f4d 100644 --- a/src/legacy/core_plugins/tests_bundle/index.js +++ b/src/legacy/core_plugins/tests_bundle/index.js @@ -23,7 +23,8 @@ import globby from 'globby'; import MultiStream from 'multistream'; import webpackMerge from 'webpack-merge'; -import { fromRoot } from '../../../legacy/utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { fromRoot } from '../../../core/server/utils'; import { replacePlaceholder } from '../../../optimize/public_path_placeholder'; import findSourceFiles from './find_source_files'; import { createTestEntryTemplate } from './tests_entry_template'; diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 17632a2c0a405c..347968ecb80c54 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -70,9 +70,9 @@ const uiCapabilities = { // Mock fetch for CoreSystem calls. fetchMock.config.fallbackToNetwork = true; -fetchMock.post(/\\/api\\/capabilities/, { +fetchMock.post(/\\/api\\/core\\/capabilities/, { status: 200, - body: JSON.stringify({ capabilities: uiCapabilities }), + body: JSON.stringify(uiCapabilities), headers: { 'Content-Type': 'application/json' }, }); diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index 14a348f6240026..52acaf51b39b1a 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -21,7 +21,7 @@ import { CoreStart, Plugin, PluginInitializerContext, - UiSettingsClientContract, + IUiSettingsClient, } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; @@ -35,7 +35,7 @@ import { createTileMapTypeDefinition } from './tile_map_type'; /** @private */ interface TileMapVisualizationDependencies extends LegacyDependenciesPluginSetup { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } /** @internal */ diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index b0123cd34b49e3..ba8c25c20abeab 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -21,7 +21,7 @@ import { CoreStart, Plugin, PluginInitializerContext, - UiSettingsClientContract, + IUiSettingsClient, HttpSetup, } from 'kibana/public'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; @@ -35,7 +35,7 @@ import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim' /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; http: HttpSetup; timelionPanels: Map; timefilter: TimefilterContract; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js b/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js index ebc187100c7e18..2aa4b9a471c482 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js @@ -95,16 +95,16 @@ describe('yaxis.js', () => { it('throws an error if currency is not three letter code', () => { invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:abcde']).catch(e => { - expect(e).to.be.an(Error); + expect(e).to.be.an.instanceof(Error); }); invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:12']).catch(e => { - expect(e).to.be.an(Error); + expect(e).to.be.an.instanceof(Error); }); invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:$#']).catch(e => { - expect(e).to.be.an(Error); + expect(e).to.be.an.instanceof(Error); }); invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:ab']).catch(e => { - expect(e).to.be.an(Error); + expect(e).to.be.an.instanceof(Error); }); }); diff --git a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js deleted file mode 100644 index e22dd4caa6d011..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.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 $ from 'jquery'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; -import { Vis } from '../../../visualizations/public'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { AppStateProvider } from 'ui/state_management/app_state'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; - -import { tableVisTypeDefinition } from '../table_vis_type'; -import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy'; - -describe('Table Vis - Controller', async function () { - let $rootScope; - let $compile; - let Private; - let $scope; - let $el; - let fixtures; - let AppState; - let tableAggResponse; - let tabifiedResponse; - - ngMock.inject(function () { - - visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); - }); - - beforeEach(ngMock.module('kibana', 'kibana/table_vis')); - beforeEach( - ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - fixtures = require('fixtures/fake_hierarchical_data'); - AppState = Private(AppStateProvider); - tableAggResponse = legacyResponseHandlerProvider().handler; - }) - ); - - function OneRangeVis(params) { - return new Vis(Private(FixturesStubbedLogstashIndexPatternProvider), { - type: 'table', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }], - }, - }, - ], - }); - } - - const dimensions = { - buckets: [ - { - accessor: 0, - }, - ], - metrics: [ - { - accessor: 1, - format: { id: 'range' }, - }, - ], - }; - - // basically a parameterized beforeEach - function initController(vis) { - vis.aggs.aggs.forEach(function (agg, i) { - agg.id = 'agg_' + (i + 1); - }); - - tabifiedResponse = tabifyAggResponse(vis.aggs, fixtures.oneRangeBucket); - $rootScope.vis = vis; - $rootScope.visParams = vis.params; - $rootScope.uiState = new AppState({ uiState: {} }).makeStateful('uiState'); - $rootScope.renderComplete = () => {}; - $rootScope.newScope = function (scope) { - $scope = scope; - }; - - $el = $('
') - .attr('ng-controller', 'KbnTableVisController') - .attr('ng-init', 'newScope(this)'); - - $compile($el)($rootScope); - } - - // put a response into the controller - function attachEsResponseToScope(resp) { - $rootScope.esResponse = resp; - $rootScope.$apply(); - } - - // remove the response from the controller - function removeEsResponseFromScope() { - delete $rootScope.esResponse; - $rootScope.renderComplete = () => {}; - $rootScope.$apply(); - } - - it('exposes #tableGroups and #hasSomeRows when a response is attached to scope', async function () { - const vis = new OneRangeVis(); - initController(vis); - - expect(!$scope.tableGroups).to.be.ok(); - expect(!$scope.hasSomeRows).to.be.ok(); - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.hasSomeRows).to.be(true); - expect($scope.tableGroups).to.have.property('tables'); - expect($scope.tableGroups.tables).to.have.length(1); - expect($scope.tableGroups.tables[0].columns).to.have.length(2); - expect($scope.tableGroups.tables[0].rows).to.have.length(2); - }); - - it('clears #tableGroups and #hasSomeRows when the response is removed', async function () { - const vis = new OneRangeVis(); - initController(vis); - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - removeEsResponseFromScope(); - - expect(!$scope.hasSomeRows).to.be.ok(); - expect(!$scope.tableGroups).to.be.ok(); - }); - - it('sets the sort on the scope when it is passed as a vis param', async function () { - const sortObj = { - columnIndex: 1, - direction: 'asc', - }; - const vis = new OneRangeVis({ sort: sortObj }); - initController(vis); - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.sort.columnIndex).to.equal(sortObj.columnIndex); - expect($scope.sort.direction).to.equal(sortObj.direction); - }); - - it('sets #hasSomeRows properly if the table group is empty', async function () { - const vis = new OneRangeVis(); - initController(vis); - - tabifiedResponse.rows = []; - - attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); - - expect($scope.hasSomeRows).to.be(false); - expect(!$scope.tableGroups).to.be.ok(); - }); - - it('passes partialRows:true to tabify based on the vis params', function () { - const vis = new OneRangeVis({ showPartialRows: true }); - initController(vis); - - expect(vis.isHierarchical()).to.equal(true); - }); - - it('passes partialRows:false to tabify based on the vis params', function () { - const vis = new OneRangeVis({ showPartialRows: false }); - initController(vis); - - expect(vis.isHierarchical()).to.equal(false); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 2978856a3511d9..25a28333b07ff9 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -23,14 +23,15 @@ import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import fixtures from 'fixtures/fake_hierarchical_data'; import sinon from 'sinon'; -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { legacyResponseHandlerProvider, tabifyAggResponse, npStart } from '../../legacy_imports'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { Vis } from '../../../../visualizations/public'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { round } from 'lodash'; +import { Vis } from '../../../../visualizations/public'; import { tableVisTypeDefinition } from '../../table_vis_type'; import { setup as visualizationsSetup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { getAngularModule } from '../../get_inner_angular'; +import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; describe('Table Vis - AggTable Directive', function () { let $rootScope; @@ -96,11 +97,18 @@ describe('Table Vis - AggTable Directive', function () { ); }; + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + ngMock.inject(function () { visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); }); - beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.module('kibana/table_vis')); beforeEach( ngMock.inject(function ($injector, Private, config) { tableAggResponse = legacyResponseHandlerProvider().handler; diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js index f4e3a8e36605cf..be981829ae909a 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js @@ -21,10 +21,11 @@ import $ from 'jquery'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import fixtures from 'fixtures/fake_hierarchical_data'; -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { legacyResponseHandlerProvider, tabifyAggResponse, npStart } from '../../legacy_imports'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { Vis } from 'ui/vis'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; +import { Vis } from '../../../../visualizations/public'; +import { getAngularModule } from '../../get_inner_angular'; +import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; describe('Table Vis - AggTableGroup Directive', function () { let $rootScope; @@ -52,7 +53,14 @@ describe('Table Vis - AggTableGroup Directive', function () { tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, fixtures.threeTermBuckets); }; - beforeEach(ngMock.module('kibana')); + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + + beforeEach(ngMock.module('kibana/table_vis')); beforeEach( ngMock.inject(function ($injector, Private) { // this is provided in table_vis_controller.js diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js index 06cca15f885568..8bc275f5255bbe 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; import aggTableTemplate from './agg_table.html'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { getFormat } from '../legacy_imports'; import { i18n } from '@kbn/i18n'; export function KbnAggTable(config, RecursionHelper) { diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index fdcd531ad6930a..4d69af59b0c99f 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -23,8 +23,7 @@ import { EuiIconTip, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { tabifyGetColumns } from 'ui/agg_response/tabify/_get_columns'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { tabifyGetColumns, VisOptionsProps } from '../legacy_imports'; import { NumberInputOption, SwitchOption, diff --git a/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts b/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts new file mode 100644 index 00000000000000..9f3a8327c9ad94 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.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. + */ + +// inner angular imports +// these are necessary to bootstrap the local angular. +// They can stay even after NP cutover +import angular from 'angular'; +import 'ui/angular-bootstrap'; +import 'angular-recursion'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; +import { + PrivateProvider, + PaginateDirectiveProvider, + PaginateControlsDirectiveProvider, + watchMultiDecorator, + KbnAccessibleClickProvider, + StateManagementConfigProvider, + configureAppAngularModule, +} from './legacy_imports'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; + +export function getAngularModule(name: string, core: CoreStart) { + const uiModule = getInnerAngular(name, core); + configureAppAngularModule(uiModule, core as LegacyCoreStart, true); + return uiModule; +} + +let initialized = false; + +export function getInnerAngular(name = 'kibana/table_vis', core: CoreStart) { + if (!initialized) { + createLocalPrivateModule(); + createLocalI18nModule(); + createLocalConfigModule(core.uiSettings); + createLocalPaginateModule(); + initialized = true; + } + return angular + .module(name, [ + ...thirdPartyAngularDependencies, + 'tableVisPaginate', + 'tableVisConfig', + 'tableVisPrivate', + 'tableVisI18n', + ]) + .config(watchMultiDecorator) + .directive('kbnAccessibleClick', KbnAccessibleClickProvider); +} + +function createLocalPrivateModule() { + angular.module('tableVisPrivate', []).provider('Private', PrivateProvider); +} + +function createLocalConfigModule(uiSettings: IUiSettingsClient) { + angular + .module('tableVisConfig', ['tableVisPrivate']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', function() { + return { + $get: () => ({ + get: (value: string) => { + return uiSettings ? uiSettings.get(value) : undefined; + }, + // set method is used in agg_table mocha test + set: (key: string, value: string) => { + return uiSettings ? uiSettings.set(key, value) : undefined; + }, + }), + }; + }); +} + +function createLocalI18nModule() { + angular + .module('tableVisI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createLocalPaginateModule() { + angular + .module('tableVisPaginate', []) + .directive('paginate', PaginateDirectiveProvider) + .directive('paginateControls', PaginateControlsDirectiveProvider); +} diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy.ts b/src/legacy/core_plugins/vis_type_table/public/legacy.ts index 8513622dec9aad..e5b2619ef29702 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_table/public/legacy.ts @@ -18,20 +18,15 @@ */ import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; +import { npSetup, npStart } from './legacy_imports'; import { plugin } from '.'; import { TablePluginSetupDependencies } from './plugin'; import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; -import { LegacyDependenciesPlugin } from './shim'; const plugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: visualizationsSetup, - - // Temporary solution - // It will be removed when all dependent services are migrated to the new platform. - __LEGACY: new LegacyDependenciesPlugin(), }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts new file mode 100644 index 00000000000000..a372eced3e34b6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/legacy_imports.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. + */ + +export { npSetup, npStart } from 'ui/new_platform'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { AggConfig } from 'ui/vis'; +export { AggGroupNames, VisOptionsProps } from 'ui/vis/editors/default'; +// @ts-ignore +export { Schemas } from 'ui/vis/editors/default/schemas'; +// @ts-ignore +export { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; + +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +export { PaginateDirectiveProvider } from 'ui/directives/paginate'; +// @ts-ignore +export { PaginateControlsDirectiveProvider } from 'ui/directives/paginate'; +// @ts-ignore +export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; + +// @ts-ignore +export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { configureAppAngularModule } from 'ui/legacy_compat'; + +export { tabifyGetColumns } from 'ui/agg_response/tabify/_get_columns'; +// @ts-ignore +export { tabifyAggResponse } from 'ui/agg_response/tabify'; diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js b/src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js deleted file mode 100644 index d146a1bddf2601..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/paginated_table/__tests__/paginated_table.js +++ /dev/null @@ -1,419 +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 $ from 'jquery'; - -describe('Table Vis - Paginated table', function () { - let $el; - let $rootScope; - let $compile; - let $scope; - const defaultPerPage = 10; - - const makeData = function (colCount, rowCount) { - let columns = []; - let rows = []; - - if (_.isNumber(colCount)) { - _.times(colCount, function (i) { - columns.push({ id: i, title: 'column' + i, formatter: { convert: _.identity } }); - }); - } else { - columns = colCount.map((col, i) => ({ - id: i, - title: col.title, - formatter: col.formatter || { convert: _.identity } - })); - } - - if (_.isNumber(rowCount)) { - _.times(rowCount, function (row) { - const rowItems = {}; - - _.times(columns.length, function (col) { - rowItems[col] = 'item' + col + row; - }); - - rows.push(rowItems); - }); - } else { - rows = rowCount.map(row => { - const newRow = {}; - row.forEach((v, i) => newRow[i] = v); - return newRow; - }); - } - - return { - columns: columns, - rows: rows - }; - }; - - const renderTable = function (table, cols, rows, perPage, sort, linkToTop) { - $scope.table = table || { columns: [], rows: [] }; - $scope.cols = cols || []; - $scope.rows = rows || []; - $scope.perPage = perPage || defaultPerPage; - $scope.sort = sort || {}; - $scope.linkToTop = linkToTop; - - const template = ` - `; - $el = $compile(template)($scope); - - $scope.$digest(); - }; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (_$rootScope_, _$compile_) { - $rootScope = _$rootScope_; - $compile = _$compile_; - $scope = $rootScope.$new(); - })); - - describe('rendering', function () { - it('should not display without rows', function () { - const cols = [{ - title: 'test1' - }]; - const rows = []; - - renderTable(null, cols, rows); - expect($el.children().length).to.be(0); - }); - - it('should render columns and rows', function () { - const data = makeData(2, 2); - const cols = data.columns; - const rows = data.rows; - - renderTable(data, cols, rows); - expect($el.children().length).to.be(1); - const tableRows = $el.find('tbody tr'); - // should contain the row data - expect(tableRows.eq(0).find('td').eq(0).text()).to.be(rows[0][0]); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be(rows[0][1]); - expect(tableRows.eq(1).find('td').eq(0).text()).to.be(rows[1][0]); - expect(tableRows.eq(1).find('td').eq(1).text()).to.be(rows[1][1]); - }); - - it('should paginate rows', function () { - // note: paginate truncates pages, so don't make too many - const rowCount = _.random(16, 24); - const perPageCount = _.random(5, 8); - const data = makeData(3, rowCount); - const pageCount = Math.ceil(rowCount / perPageCount); - - renderTable(data, data.columns, data.rows, perPageCount); - const tableRows = $el.find('tbody tr'); - expect(tableRows.length).to.be(perPageCount); - // add 2 for the first and last page links - expect($el.find('paginate-controls button').length).to.be(pageCount + 2); - }); - - it('should not show blank rows on last page', function () { - const rowCount = 7; - const perPageCount = 10; - const data = makeData(3, rowCount); - - renderTable(data, data.columns, data.rows, perPageCount, null); - const tableRows = $el.find('tbody tr'); - expect(tableRows.length).to.be(rowCount); - }); - - it('should not show link to top when not set', function () { - const data = makeData(5, 5); - renderTable(data, data.columns, data.rows, 10, null); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.length).to.be(0); - }); - - it('should show link to top when set', function () { - const data = makeData(5, 5); - renderTable(data, data.columns, data.rows, 10, null, true); - - const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); - expect(linkToTop.length).to.be(1); - }); - - }); - - describe('sorting', function () { - let data; - let lastRowIndex; - let paginatedTable; - - beforeEach(function () { - data = makeData(3, [ - ['bbbb', 'aaaa', 'zzzz'], - ['cccc', 'cccc', 'aaaa'], - ['zzzz', 'bbbb', 'bbbb'], - ['aaaa', 'zzzz', 'cccc'], - ]); - - lastRowIndex = data.rows.length - 1; - renderTable(data, data.columns, data.rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - // afterEach(function () { - // $scope.$destroy(); - // }); - - it('should not sort by default', function () { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be(data.rows[0][0]); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be(data.rows[lastRowIndex][0]); - }); - - it('should do nothing when sorting by invalid column id', function () { - // sortColumn - paginatedTable.sortColumn(999); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('zzzz'); - }); - - it('should do nothing when sorting by non sortable column', function () { - data.columns[0].sortable = false; - - // sortColumn - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('zzzz'); - }); - - it('should set the sort direction to asc when it\'s not explicitly set', function () { - paginatedTable.sortColumn(1); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(2).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(1).find('td').eq(1).text()).to.be('bbbb'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - }); - - it('should allow you to explicitly set the sort direction', function () { - paginatedTable.sortColumn(1, 'desc'); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('zzzz'); - expect(tableRows.eq(1).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(2).find('td').eq(1).text()).to.be('bbbb'); - }); - - it('should sort ascending on first invocation', function () { - // sortColumn - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('aaaa'); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('zzzz'); - }); - - it('should sort descending on second invocation', function () { - // sortColumn - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('zzzz'); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('aaaa'); - }); - - it('should clear sorting on third invocation', function () { - // sortColumn - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - paginatedTable.sortColumn(0); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be(data.rows[0][0]); - expect(tableRows.eq(lastRowIndex).find('td').eq(0).text()).to.be('aaaa'); - }); - - it('should sort new column ascending', function () { - // sort by first column - paginatedTable.sortColumn(0); - $scope.$digest(); - - // sort by second column - paginatedTable.sortColumn(1); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('aaaa'); - expect(tableRows.eq(lastRowIndex).find('td').eq(1).text()).to.be('zzzz'); - }); - - }); - - describe('sorting duplicate columns', function () { - let data; - let paginatedTable; - const colText = 'test row'; - - beforeEach(function () { - const cols = [ - { title: colText }, - { title: colText }, - { title: colText } - ]; - const rows = [ - ['bbbb', 'aaaa', 'zzzz'], - ['cccc', 'cccc', 'aaaa'], - ['zzzz', 'bbbb', 'bbbb'], - ['aaaa', 'zzzz', 'cccc'], - ]; - data = makeData(cols, rows); - - renderTable(data, data.columns, data.rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - it('should have duplicate column titles', function () { - const columns = $el.find('thead th span'); - columns.each(function () { - expect($(this).text()).to.be(colText); - }); - }); - - it('should handle sorting on columns with the same name', function () { - // sort by the last column - paginatedTable.sortColumn(2); - $scope.$digest(); - - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('aaaa'); - expect(tableRows.eq(1).find('td').eq(2).text()).to.be('bbbb'); - expect(tableRows.eq(2).find('td').eq(2).text()).to.be('cccc'); - expect(tableRows.eq(3).find('td').eq(2).text()).to.be('zzzz'); - }); - - it('should sort correctly between columns', function () { - // sort by the last column - paginatedTable.sortColumn(2); - $scope.$digest(); - - let tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('cccc'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('aaaa'); - - // sort by the first column - paginatedTable.sortColumn(0); - $scope.$digest(); - - tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('td').eq(0).text()).to.be('aaaa'); - expect(tableRows.eq(0).find('td').eq(1).text()).to.be('zzzz'); - expect(tableRows.eq(0).find('td').eq(2).text()).to.be('cccc'); - - expect(tableRows.eq(1).find('td').eq(0).text()).to.be('bbbb'); - expect(tableRows.eq(2).find('td').eq(0).text()).to.be('cccc'); - expect(tableRows.eq(3).find('td').eq(0).text()).to.be('zzzz'); - }); - - it('should not sort duplicate columns', function () { - paginatedTable.sortColumn(1); - $scope.$digest(); - - const sorters = $el.find('thead th i'); - expect(sorters.eq(0).hasClass('fa-sort')).to.be(true); - expect(sorters.eq(1).hasClass('fa-sort')).to.be(false); - expect(sorters.eq(2).hasClass('fa-sort')).to.be(true); - }); - - }); - - - describe('object rows', function () { - let cols; - let rows; - let paginatedTable; - - beforeEach(function () { - cols = [{ - title: 'object test', - id: 0, - formatter: { convert: val => { - return val === 'zzz' ? '

hello

' : val; - } } - }]; - rows = [ - ['aaaa'], - ['zzz'], - ['bbbb'] - ]; - renderTable({ columns: cols, rows }, cols, rows); - paginatedTable = $el.isolateScope().paginatedTable; - }); - - it('should append object markup', function () { - const tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').length).to.be(0); - expect(tableRows.eq(1).find('h1').length).to.be(1); - expect(tableRows.eq(2).find('h1').length).to.be(0); - }); - - it('should sort using object value', function () { - paginatedTable.sortColumn(0); - $scope.$digest(); - let tableRows = $el.find('tbody tr'); - expect(tableRows.eq(0).find('h1').length).to.be(0); - expect(tableRows.eq(1).find('h1').length).to.be(0); - // html row should be the last row - expect(tableRows.eq(2).find('h1').length).to.be(1); - - paginatedTable.sortColumn(0); - $scope.$digest(); - tableRows = $el.find('tbody tr'); - // html row should be the first row - expect(tableRows.eq(0).find('h1').length).to.be(1); - expect(tableRows.eq(1).find('h1').length).to.be(0); - expect(tableRows.eq(2).find('h1').length).to.be(0); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts b/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts new file mode 100644 index 00000000000000..781782e42fbaf2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts @@ -0,0 +1,713 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isNumber, times, identity, random } from 'lodash'; +import angular, { IRootScopeService, IScope, ICompileService } from 'angular'; +import $ from 'jquery'; +import 'angular-sanitize'; +import 'angular-mocks'; +import '../table_vis.mock'; + +import { getAngularModule } from '../get_inner_angular'; +import { initTableVisLegacyModule } from '../table_vis_legacy_module'; +import { npStart } from '../legacy_imports'; + +interface Sort { + columnIndex: number; + direction: string; +} + +interface Row { + [key: string]: number | string; +} + +interface Column { + id?: string; + title: string; + formatter?: { + convert?: (val: string) => string; + }; + sortable?: boolean; +} + +interface Table { + columns: Column[]; + rows: Row[]; +} + +interface PaginatedTableScope extends IScope { + table?: Table; + cols?: Column[]; + rows?: Row[]; + perPage?: number; + sort?: Sort; + linkToTop?: boolean; +} + +describe('Table Vis - Paginated table', () => { + let $el: JQuery; + let $rootScope: IRootScopeService; + let $compile: ICompileService; + let $scope: PaginatedTableScope; + const defaultPerPage = 10; + let paginatedTable: any; + + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + beforeEach(angular.mock.module('kibana/table_vis')); + + beforeEach( + angular.mock.inject((_$rootScope_: IRootScopeService, _$compile_: ICompileService) => { + $rootScope = _$rootScope_; + $compile = _$compile_; + $scope = $rootScope.$new(); + }) + ); + + afterEach(() => { + $scope.$destroy(); + }); + + const makeData = (colCount: number | Column[], rowCount: number | string[][]) => { + let columns: Column[] = []; + let rows: Row[] = []; + + if (isNumber(colCount)) { + times(colCount, i => { + columns.push({ id: `${i}`, title: `column${i}`, formatter: { convert: identity } }); + }); + } else { + columns = colCount.map( + (col, i) => + ({ + id: `${i}`, + title: col.title, + formatter: col.formatter || { convert: identity }, + } as Column) + ); + } + + if (isNumber(rowCount)) { + times(rowCount, row => { + const rowItems: Row = {}; + + times(columns.length, col => { + rowItems[`${col}`] = `item-${col}-${row}`; + }); + + rows.push(rowItems); + }); + } else { + rows = rowCount.map((row: string[]) => { + const newRow: Row = {}; + row.forEach((v, i) => (newRow[i] = v)); + return newRow; + }); + } + + return { + columns, + rows, + }; + }; + + const renderTable = ( + table: { columns: Column[]; rows: Row[] } | null, + cols: Column[], + rows: Row[], + perPage?: number, + sort?: Sort, + linkToTop?: boolean + ) => { + $scope.table = table || { columns: [], rows: [] }; + $scope.cols = cols || []; + $scope.rows = rows || []; + $scope.perPage = perPage || defaultPerPage; + $scope.sort = sort; + $scope.linkToTop = linkToTop; + + const template = ` + `; + const element = $compile(template)($scope); + $el = $(element); + + $scope.$digest(); + paginatedTable = element.controller('paginatedTable'); + }; + + describe('rendering', () => { + test('should not display without rows', () => { + const cols: Column[] = [ + { + id: 'col-1-1', + title: 'test1', + }, + ]; + const rows: Row[] = []; + + renderTable(null, cols, rows); + expect($el.children().length).toBe(0); + }); + + test('should render columns and rows', () => { + const data = makeData(2, 2); + const cols = data.columns; + const rows = data.rows; + + renderTable(data, cols, rows); + expect($el.children().length).toBe(1); + const tableRows = $el.find('tbody tr'); + + // should contain the row data + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe(rows[0][0]); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe(rows[0][1]); + expect( + tableRows + .eq(1) + .find('td') + .eq(0) + .text() + ).toBe(rows[1][0]); + expect( + tableRows + .eq(1) + .find('td') + .eq(1) + .text() + ).toBe(rows[1][1]); + }); + + test('should paginate rows', () => { + // note: paginate truncates pages, so don't make too many + const rowCount = random(16, 24); + const perPageCount = random(5, 8); + const data = makeData(3, rowCount); + const pageCount = Math.ceil(rowCount / perPageCount); + + renderTable(data, data.columns, data.rows, perPageCount); + const tableRows = $el.find('tbody tr'); + expect(tableRows.length).toBe(perPageCount); + // add 2 for the first and last page links + expect($el.find('paginate-controls button').length).toBe(pageCount + 2); + }); + + test('should not show blank rows on last page', () => { + const rowCount = 7; + const perPageCount = 10; + const data = makeData(3, rowCount); + + renderTable(data, data.columns, data.rows, perPageCount); + const tableRows = $el.find('tbody tr'); + expect(tableRows.length).toBe(rowCount); + }); + + test('should not show link to top when not set', () => { + const data = makeData(5, 5); + renderTable(data, data.columns, data.rows, 10); + + const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); + expect(linkToTop.length).toBe(0); + }); + + test('should show link to top when set', () => { + const data = makeData(5, 5); + renderTable(data, data.columns, data.rows, 10, undefined, true); + + const linkToTop = $el.find('[data-test-subj="paginateControlsLinkToTop"]'); + expect(linkToTop.length).toBe(1); + }); + }); + + describe('sorting', () => { + let data: Table; + let lastRowIndex: number; + + beforeEach(() => { + data = makeData(3, [ + ['bbbb', 'aaaa', 'zzzz'], + ['cccc', 'cccc', 'aaaa'], + ['zzzz', 'bbbb', 'bbbb'], + ['aaaa', 'zzzz', 'cccc'], + ]); + + lastRowIndex = data.rows.length - 1; + renderTable(data, data.columns, data.rows); + }); + + test('should not sort by default', () => { + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe(data.rows[0][0]); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe(data.rows[lastRowIndex][0]); + }); + + test('should do nothing when sorting by invalid column id', () => { + // sortColumn + paginatedTable.sortColumn(999); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('zzzz'); + }); + + test('should do nothing when sorting by non sortable column', () => { + data.columns[0].sortable = false; + + // sortColumn + paginatedTable.sortColumn(0); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('zzzz'); + }); + + test("should set the sort direction to asc when it's not explicitly set", () => { + paginatedTable.sortColumn(1); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(2) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(1) + .find('td') + .eq(1) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + }); + + test('should allow you to explicitly set the sort direction', () => { + paginatedTable.sortColumn(1, 'desc'); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('zzzz'); + expect( + tableRows + .eq(1) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(2) + .find('td') + .eq(1) + .text() + ).toBe('bbbb'); + }); + + test('should sort ascending on first invocation', () => { + // sortColumn + paginatedTable.sortColumn(0); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe('zzzz'); + }); + + test('should sort descending on second invocation', () => { + // sortColumn + paginatedTable.sortColumn(0); + paginatedTable.sortColumn(0); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('zzzz'); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + }); + + test('should clear sorting on third invocation', () => { + // sortColumn + paginatedTable.sortColumn(0); + paginatedTable.sortColumn(0); + paginatedTable.sortColumn(0); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe(data.rows[0][0]); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + }); + + test('should sort new column ascending', () => { + // sort by first column + paginatedTable.sortColumn(0); + $scope.$digest(); + + // sort by second column + paginatedTable.sortColumn(1); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(lastRowIndex) + .find('td') + .eq(1) + .text() + ).toBe('zzzz'); + }); + }); + + describe('sorting duplicate columns', () => { + let data; + const colText = 'test row'; + + beforeEach(() => { + const cols: Column[] = [{ title: colText }, { title: colText }, { title: colText }]; + const rows = [ + ['bbbb', 'aaaa', 'zzzz'], + ['cccc', 'cccc', 'aaaa'], + ['zzzz', 'bbbb', 'bbbb'], + ['aaaa', 'zzzz', 'cccc'], + ]; + data = makeData(cols, rows); + + renderTable(data, data.columns, data.rows); + }); + + test('should have duplicate column titles', () => { + const columns = $el.find('thead th span'); + columns.each((i, col) => { + expect($(col).text()).toBe(colText); + }); + }); + + test('should handle sorting on columns with the same name', () => { + // sort by the last column + paginatedTable.sortColumn(2); + $scope.$digest(); + + const tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(1) + .find('td') + .eq(2) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(2) + .find('td') + .eq(2) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(3) + .find('td') + .eq(2) + .text() + ).toBe('zzzz'); + }); + + test('should sort correctly between columns', () => { + // sort by the last column + paginatedTable.sortColumn(2); + $scope.$digest(); + + let tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('aaaa'); + + // sort by the first column + paginatedTable.sortColumn(0); + $scope.$digest(); + + tableRows = $el.find('tbody tr'); + expect( + tableRows + .eq(0) + .find('td') + .eq(0) + .text() + ).toBe('aaaa'); + expect( + tableRows + .eq(0) + .find('td') + .eq(1) + .text() + ).toBe('zzzz'); + expect( + tableRows + .eq(0) + .find('td') + .eq(2) + .text() + ).toBe('cccc'); + + expect( + tableRows + .eq(1) + .find('td') + .eq(0) + .text() + ).toBe('bbbb'); + expect( + tableRows + .eq(2) + .find('td') + .eq(0) + .text() + ).toBe('cccc'); + expect( + tableRows + .eq(3) + .find('td') + .eq(0) + .text() + ).toBe('zzzz'); + }); + + test('should not sort duplicate columns', () => { + paginatedTable.sortColumn(1); + $scope.$digest(); + + const sorters = $el.find('thead th i'); + expect(sorters.eq(0).hasClass('fa-sort')).toBe(true); + expect(sorters.eq(1).hasClass('fa-sort')).toBe(false); + expect(sorters.eq(2).hasClass('fa-sort')).toBe(true); + }); + }); + + describe('object rows', () => { + let cols: Column[]; + let rows: any; + + beforeEach(() => { + cols = [ + { + title: 'object test', + id: '0', + formatter: { + convert: val => { + return val === 'zzz' ? '

hello

' : val; + }, + }, + }, + ]; + rows = [['aaaa'], ['zzz'], ['bbbb']]; + renderTable({ columns: cols, rows }, cols, rows); + }); + + test('should append object markup', () => { + const tableRows = $el.find('tbody tr'); + expect(tableRows.eq(0).find('h1').length).toBe(0); + expect(tableRows.eq(1).find('h1').length).toBe(1); + expect(tableRows.eq(2).find('h1').length).toBe(0); + }); + + test('should sort using object value', () => { + paginatedTable.sortColumn(0); + $scope.$digest(); + let tableRows = $el.find('tbody tr'); + expect(tableRows.eq(0).find('h1').length).toBe(0); + expect(tableRows.eq(1).find('h1').length).toBe(0); + // html row should be the last row + expect(tableRows.eq(2).find('h1').length).toBe(1); + + paginatedTable.sortColumn(0); + $scope.$digest(); + tableRows = $el.find('tbody tr'); + // html row should be the first row + expect(tableRows.eq(0).find('h1').length).toBe(1); + expect(tableRows.eq(1).find('h1').length).toBe(0); + expect(tableRows.eq(2).find('h1').length).toBe(0); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index ce8d349d8dd7aa..17c50b0567b67d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -21,8 +21,6 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { LegacyDependenciesPlugin } from './shim'; - import { createTableVisFn } from './table_vis_fn'; import { tableVisTypeDefinition } from './table_vis_type'; @@ -30,7 +28,6 @@ import { tableVisTypeDefinition } from './table_vis_type'; export interface TablePluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; - __LEGACY: LegacyDependenciesPlugin; } /** @internal */ @@ -43,9 +40,8 @@ export class TableVisPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { expressions, visualizations, __LEGACY }: TablePluginSetupDependencies + { expressions, visualizations }: TablePluginSetupDependencies ) { - __LEGACY.setup(); expressions.registerFunction(createTableVisFn); visualizations.types.createBaseVisualization(tableVisTypeDefinition); diff --git a/src/legacy/core_plugins/vis_type_table/public/shim/index.ts b/src/legacy/core_plugins/vis_type_table/public/shim/index.ts deleted file mode 100644 index cfc7b62ff4f86d..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/shim/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 './legacy_dependencies_plugin'; diff --git a/src/legacy/core_plugins/vis_type_table/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/vis_type_table/public/shim/legacy_dependencies_plugin.ts deleted file mode 100644 index ba30664951f473..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/shim/legacy_dependencies_plugin.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 { CoreStart, Plugin } from '../../../../../core/public'; -import { initTableVisLegacyModule } from './table_vis_legacy_module'; - -/** @internal */ -export class LegacyDependenciesPlugin implements Plugin { - public setup() { - initTableVisLegacyModule(); - } - - public start(core: CoreStart) { - // nothing to do here yet - } -} diff --git a/src/legacy/core_plugins/vis_type_table/public/shim/table_vis_legacy_module.ts b/src/legacy/core_plugins/vis_type_table/public/shim/table_vis_legacy_module.ts deleted file mode 100644 index e148fd98490df2..00000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/shim/table_vis_legacy_module.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 { once } from 'lodash'; - -// @ts-ignore -import { uiModules } from 'ui/modules'; - -import 'angular-recursion'; -import 'ui/directives/paginate'; - -// @ts-ignore -import { TableVisController } from '../table_vis_controller.js'; -// @ts-ignore -import { KbnAggTable } from '../agg_table/agg_table'; -// @ts-ignore -import { KbnAggTableGroup } from '../agg_table/agg_table_group'; -// @ts-ignore -import { KbnRows } from '../paginated_table/rows'; -// @ts-ignore -import { PaginatedTable } from '../paginated_table/paginated_table'; - -/** @internal */ -export const initTableVisLegacyModule = once((): void => { - uiModules - .get('kibana/table_vis', ['kibana', 'RecursionHelper']) - .controller('KbnTableVisController', TableVisController) - .directive('kbnAggTable', KbnAggTable) - .directive('kbnAggTableGroup', KbnAggTableGroup) - .directive('kbnRows', KbnRows) - .directive('paginatedTable', PaginatedTable); -}); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts new file mode 100644 index 00000000000000..d04964cb7af038 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.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 { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { injectedMetadataServiceMock } from '../../../../core/public/mocks'; + +jest.doMock('ui/new_platform', () => { + const npMock = createUiNewPlatformMock(); + return { + npSetup: { + ...npMock.npSetup, + core: { + ...npMock.npSetup.core, + injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + }, + }, + npStart: { + ...npMock.npStart, + core: { + ...npMock.npStart.core, + injectedMetadata: injectedMetadataServiceMock.createStartContract(), + }, + }, + }; +}); + +Object.assign(window, { + sessionStorage: new StubBrowserStorage(), +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts new file mode 100644 index 00000000000000..a2f98b8c64e538 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -0,0 +1,257 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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, { IRootScopeService, IScope, ICompileService } from 'angular'; +import 'angular-mocks'; +import 'angular-sanitize'; +import $ from 'jquery'; +import './table_vis.mock'; + +// @ts-ignore +import StubIndexPattern from 'test_utils/stub_index_pattern'; +import { getAngularModule } from './get_inner_angular'; +import { initTableVisLegacyModule } from './table_vis_legacy_module'; +import { + npStart, + legacyResponseHandlerProvider, + AggConfig, + tabifyAggResponse, +} from './legacy_imports'; +import { tableVisTypeDefinition } from './table_vis_type'; +import { Vis } from '../../visualizations/public'; +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +// eslint-disable-next-line +import { stubFields } from '../../../../plugins/data/public/stubs'; +// eslint-disable-next-line +import { setFieldFormats } from '../../../../plugins/data/public/services'; + +interface TableVisScope extends IScope { + [key: string]: any; +} + +const oneRangeBucket = { + hits: { + total: 6039, + max_score: 0, + hits: [], + }, + aggregations: { + agg_2: { + buckets: { + '0.0-1000.0': { + from: 0, + from_as_string: '0.0', + to: 1000, + to_as_string: '1000.0', + doc_count: 606, + }, + '1000.0-2000.0': { + from: 1000, + from_as_string: '1000.0', + to: 2000, + to_as_string: '2000.0', + doc_count: 298, + }, + }, + }, + }, +}; + +describe('Table Vis - Controller', () => { + let $rootScope: IRootScopeService & { [key: string]: any }; + let $compile: ICompileService; + let $scope: TableVisScope; + let $el: JQuery; + let tableAggResponse: any; + let tabifiedResponse: any; + let stubIndexPattern: any; + + const initLocalAngular = () => { + const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + initTableVisLegacyModule(tableVisModule); + }; + + beforeEach(initLocalAngular); + beforeAll(() => { + visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); + }); + beforeEach(angular.mock.module('kibana/table_vis')); + + beforeEach( + angular.mock.inject((_$rootScope_: IRootScopeService, _$compile_: ICompileService) => { + $rootScope = _$rootScope_; + $compile = _$compile_; + tableAggResponse = legacyResponseHandlerProvider().handler; + }) + ); + + beforeEach(() => { + setFieldFormats(({ + getDefaultInstance: jest.fn(), + } as unknown) as any); + stubIndexPattern = new StubIndexPattern( + 'logstash-*', + (cfg: any) => cfg, + 'time', + stubFields, + npStart.core.uiSettings + ); + }); + + function getRangeVis(params?: object) { + // @ts-ignore + return new Vis(stubIndexPattern, { + type: 'table', + params: params || {}, + aggs: [ + { type: 'count', schema: 'metric' }, + { + type: 'range', + schema: 'bucket', + params: { + field: 'bytes', + ranges: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 }, + ], + }, + }, + ], + }); + } + + const dimensions = { + buckets: [ + { + accessor: 0, + }, + ], + metrics: [ + { + accessor: 1, + format: { id: 'range' }, + }, + ], + }; + + // basically a parameterized beforeEach + function initController(vis: Vis) { + vis.aggs.aggs.forEach((agg: AggConfig, i: number) => { + agg.id = 'agg_' + (i + 1); + }); + + tabifiedResponse = tabifyAggResponse(vis.aggs, oneRangeBucket); + $rootScope.vis = vis; + $rootScope.visParams = vis.params; + $rootScope.uiState = { + get: jest.fn(), + set: jest.fn(), + }; + $rootScope.renderComplete = () => {}; + $rootScope.newScope = (scope: TableVisScope) => { + $scope = scope; + }; + + $el = $('
') + .attr('ng-controller', 'KbnTableVisController') + .attr('ng-init', 'newScope(this)'); + + $compile($el)($rootScope); + } + + // put a response into the controller + function attachEsResponseToScope(resp: object) { + $rootScope.esResponse = resp; + $rootScope.$apply(); + } + + // remove the response from the controller + function removeEsResponseFromScope() { + delete $rootScope.esResponse; + $rootScope.renderComplete = () => {}; + $rootScope.$apply(); + } + + test('exposes #tableGroups and #hasSomeRows when a response is attached to scope', async () => { + const vis: Vis = getRangeVis(); + initController(vis); + + expect(!$scope.tableGroups).toBeTruthy(); + expect(!$scope.hasSomeRows).toBeTruthy(); + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + + expect($scope.hasSomeRows).toBeTruthy(); + expect($scope.tableGroups.tables).toBeDefined(); + expect($scope.tableGroups.tables.length).toBe(1); + expect($scope.tableGroups.tables[0].columns.length).toBe(2); + expect($scope.tableGroups.tables[0].rows.length).toBe(2); + }); + + test('clears #tableGroups and #hasSomeRows when the response is removed', async () => { + const vis = getRangeVis(); + initController(vis); + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + removeEsResponseFromScope(); + + expect(!$scope.hasSomeRows).toBeTruthy(); + expect(!$scope.tableGroups).toBeTruthy(); + }); + + test('sets the sort on the scope when it is passed as a vis param', async () => { + const sortObj = { + columnIndex: 1, + direction: 'asc', + }; + const vis = getRangeVis({ sort: sortObj }); + initController(vis); + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + + expect($scope.sort.columnIndex).toEqual(sortObj.columnIndex); + expect($scope.sort.direction).toEqual(sortObj.direction); + }); + + test('sets #hasSomeRows properly if the table group is empty', async () => { + const vis = getRangeVis(); + initController(vis); + + tabifiedResponse.rows = []; + + attachEsResponseToScope(await tableAggResponse(tabifiedResponse, dimensions)); + + expect($scope.hasSomeRows).toBeFalsy(); + expect(!$scope.tableGroups).toBeTruthy(); + }); + + test('passes partialRows:true to tabify based on the vis params', () => { + const vis = getRangeVis({ showPartialRows: true }); + initController(vis); + + expect(vis.isHierarchical()).toEqual(true); + }); + + test('passes partialRows:false to tabify based on the vis params', () => { + const vis = getRangeVis({ showPartialRows: false }); + initController(vis); + + expect(vis.isHierarchical()).toEqual(false); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts index d909842a71e2d1..c1def9b55aae20 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts @@ -22,9 +22,7 @@ import { createTableVisFn } from './table_vis_fn'; // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; -jest.mock('ui/new_platform'); - -jest.mock('ui/vis/response_handlers/legacy', () => { +jest.mock('./legacy_imports', () => { const mockResponseHandler = jest.fn().mockReturnValue( Promise.resolve({ tables: [{ columns: [], rows: [] }], @@ -37,7 +35,7 @@ jest.mock('ui/vis/response_handlers/legacy', () => { }; }); -const { mockResponseHandler } = jest.requireMock('ui/vis/response_handlers/legacy'); +const { mockResponseHandler } = jest.requireMock('./legacy_imports'); describe('interpreter/functions#table', () => { const fn = functionWrapper(createTableVisFn); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.ts new file mode 100644 index 00000000000000..57d8b7c448b4c4 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_legacy_module.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 { IModule } from 'angular'; + +// @ts-ignore +import { TableVisController } from './table_vis_controller.js'; +// @ts-ignore +import { KbnAggTable } from './agg_table/agg_table'; +// @ts-ignore +import { KbnAggTableGroup } from './agg_table/agg_table_group'; +// @ts-ignore +import { KbnRows } from './paginated_table/rows'; +// @ts-ignore +import { PaginatedTable } from './paginated_table/paginated_table'; + +/** @internal */ +export const initTableVisLegacyModule = (angularIns: IModule): void => { + angularIns + .controller('KbnTableVisController', TableVisController) + .directive('kbnAggTable', KbnAggTable) + .directive('kbnAggTableGroup', KbnAggTableGroup) + .directive('kbnRows', KbnRows) + .directive('paginatedTable', PaginatedTable); +}; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts index c432dfd84cbb83..1a6d4600025f19 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_request_handler.ts @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore -import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { legacyResponseHandlerProvider } from './legacy_imports'; export const tableVisResponseHandler = legacyResponseHandlerProvider().handler; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts index 7e8537a1fee54d..5186b6cf59dfdd 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts @@ -18,18 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Vis } from 'ui/vis'; -// @ts-ignore - -// @ts-ignore -import { Schemas } from 'ui/vis/editors/default/schemas'; -// @ts-ignore -import { AngularVisController } from 'ui/vis/vis_types/angular_vis_type'; -import { AggGroupNames } from 'ui/vis/editors/default'; +import { AggGroupNames, Schemas } from './legacy_imports'; +import { Vis } from '../../visualizations/public'; import { tableVisResponseHandler } from './table_vis_request_handler'; // @ts-ignore import tableVisTemplate from './table_vis.html'; import { TableOptions } from './components/table_vis_options'; +import { TableVisualizationController } from './vis_controller'; export const tableVisTypeDefinition = { type: 'table', @@ -41,7 +36,7 @@ export const tableVisTypeDefinition = { description: i18n.translate('visTypeTable.tableVisDescription', { defaultMessage: 'Display values in a table', }), - visualization: AngularVisController, + visualization: TableVisualizationController, visConfig: { defaults: { perPage: 10, diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts new file mode 100644 index 00000000000000..7adaa21cac5938 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.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 angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; +import $ from 'jquery'; + +import { Vis, VisParams } from '../../visualizations/public'; +import { npStart } from './legacy_imports'; +import { getAngularModule } from './get_inner_angular'; +import { initTableVisLegacyModule } from './table_vis_legacy_module'; + +const innerAngularName = 'kibana/table_vis'; + +export class TableVisualizationController { + private tableVisModule: IModule | undefined; + private injector: auto.IInjectorService | undefined; + el: JQuery; + vis: Vis; + $rootScope: IRootScopeService | null = null; + $scope: (IScope & { [key: string]: any }) | undefined; + $compile: ICompileService | undefined; + + constructor(domeElement: Element, vis: Vis) { + this.el = $(domeElement); + this.vis = vis; + } + + getInjector() { + if (!this.injector) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); + this.injector = angular.bootstrap(mountpoint, [innerAngularName]); + this.el.append(mountpoint); + } + + return this.injector; + } + + initLocalAngular() { + if (!this.tableVisModule) { + this.tableVisModule = getAngularModule(innerAngularName, npStart.core); + initTableVisLegacyModule(this.tableVisModule); + } + } + + async render(esResponse: object, visParams: VisParams, status: { [key: string]: boolean }) { + this.initLocalAngular(); + + return new Promise(async (resolve, reject) => { + if (!this.$rootScope) { + const $injector = this.getInjector(); + this.$rootScope = $injector.get('$rootScope'); + this.$compile = $injector.get('$compile'); + } + const updateScope = () => { + if (!this.$scope) { + return; + } + this.$scope.vis = this.vis; + this.$scope.visState = this.vis.getState(); + this.$scope.esResponse = esResponse; + this.$scope.visParams = visParams; + this.$scope.renderComplete = resolve; + this.$scope.renderFailed = reject; + this.$scope.resize = Date.now(); + this.$scope.updateStatus = status; + this.$scope.$apply(); + }; + + if (!this.$scope && this.$compile) { + this.$scope = this.$rootScope.$new(); + this.$scope.uiState = this.vis.getUiState(); + updateScope(); + this.el.find('div').append(this.$compile(this.vis.type.visConfig.template)(this.$scope)); + this.$scope.$apply(); + } else { + updateScope(); + } + }); + } + + destroy() { + if (this.$rootScope) { + this.$rootScope.$destroy(); + this.$rootScope = null; + } + } +} diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss b/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss index aefebe8117bbfc..8d3aea2a3ae549 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss +++ b/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss @@ -1,6 +1,3 @@ -@import 'node_modules/@elastic/eui/src/components/form/variables'; -@import 'node_modules/@elastic/eui/src/components/form/mixins'; - @mixin tvbEditorRepeatingRow { background-color: $euiColorLightestShade; padding: $euiSizeS; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss b/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss index 3372367be273dd..99e024c9c51934 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss @@ -1,11 +1,9 @@ -@import 'node_modules/@elastic/eui/src/components/panel/mixins'; - .tvbAnnotationsEditor__container { padding: $euiSize; background-color: $euiColorLightestShade; } -@include euiPanel('tvbAnnotationsEditor'); +@include euiPanel('.tvbAnnotationsEditor'); .tvbAnnotationsEditor { margin-bottom: $euiSize; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss b/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss index 0cfe5fef010d33..82d1f4ea62676e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss @@ -1,10 +1,8 @@ -@import 'node_modules/@elastic/eui/src/components/panel/mixins'; - .tvbSeriesEditor__container { padding: $euiSize; } -@include euiPanel('tvbSeriesEditor'); +@include euiPanel('.tvbSeriesEditor'); .tvbSeriesEditor { margin-bottom: $euiSize; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js index 2eefeb35c26ff8..76306ecf28994c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js @@ -42,7 +42,7 @@ import { } from '@elastic/eui'; export class CalculationAgg extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.variables) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js index a73460798ebd81..c62012927f951b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js @@ -42,7 +42,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; export class MathAgg extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.variables) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js index ec16a0f2eb3eec..3ce5be5b6875a8 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js @@ -42,7 +42,7 @@ const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; export class PercentileAgg extends Component { // eslint-disable-line react/no-multi-comp - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.percentiles) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js index fcd80ae8844c07..d649777b56438b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/color_rules.js @@ -46,7 +46,7 @@ class ColorRulesUI extends Component { const part = {}; part[name] = cast(_.get(e, '[0].value', _.get(e, 'target.value'))); if (part[name] === 'undefined') part[name] = undefined; - if (part[name] === NaN) part[name] = undefined; + if (isNaN(part[name])) part[name] = undefined; handleChange(_.assign({}, item, part)); }; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js index 9fe237d9cb6713..835628368efab6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js @@ -18,7 +18,7 @@ */ import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { ColorWrap as colorWrap, @@ -32,18 +32,13 @@ import ChromePointer from 'react-color/lib/components/chrome/ChromePointer'; import ChromePointerCircle from 'react-color/lib/components/chrome/ChromePointerCircle'; import CompactColor from 'react-color/lib/components/compact/CompactColor'; import color from 'react-color/lib/helpers/color'; -import shallowCompare from 'react-addons-shallow-compare'; -class CustomColorPickerUI extends Component { +class CustomColorPickerUI extends PureComponent { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } - shouldComponentUpdate(nextProps, nextState) { - return shallowCompare(nextProps, nextState); - } - handleChange(data) { this.props.onChange(data); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js index 6da10f6a808165..19770519ef010a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js @@ -52,7 +52,7 @@ class GaugePanelConfigUi extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if ( diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js index 705adc07e7314c..526649766a008a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js @@ -48,7 +48,7 @@ export class MetricPanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; if ( !model.background_color_rules || diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js index 029962f189afff..3acaa728bb50fc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js @@ -51,7 +51,7 @@ export class TablePanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js index 8f5619909754e6..7dbecb9e674b01 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js @@ -51,7 +51,7 @@ export class TopNPanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js index dc976beeca0d14..3d11744355738e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js @@ -19,7 +19,7 @@ import React, { useContext } from 'react'; import { CoreStartContext } from '../contexts/query_input_bar_context'; -import { QueryStringInput } from 'plugins/data'; +import { QueryStringInput } from '../../../../../plugins/data/public'; export function QueryBarWrapper(props) { const coreStartContext = useContext(CoreStartContext); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js index fe91cb39f4acec..d1c53899db8793 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js @@ -37,7 +37,7 @@ const SPLIT_MODES = { }; export class Split extends Component { - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { model } = nextProps; if (model.split_mode === 'filters' && !model.split_filters) { this.props.onChange({ 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 1d42b773369334..ae39d75c7a2d1d 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 @@ -44,7 +44,6 @@ const APP_NAME = 'VisEditor'; export class VisEditor extends Component { constructor(props) { super(props); - this.appState = props.appState; this.localStorage = new Storage(window.localStorage); this.state = { model: props.visParams, @@ -82,6 +81,12 @@ export class VisEditor extends Component { updateVisState = debounce(() => { this.props.vis.params = this.state.model; this.props.vis.updateState(); + // This check should be redundant, since this method should only be called when we're in editor + // mode where there's also an appState passed into us. + if (this.props.appState) { + this.props.appState.vis = this.props.vis.getState(); + this.props.appState.save(); + } }, VIS_STATE_DEBOUNCE_DELAY); isValidKueryQuery = filterQuery => { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js index ae82dc41fa9bc9..10c17dbfd5ace5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.js @@ -70,12 +70,12 @@ class VisEditorVisualizationUI extends Component { return; } - const { timeRange, appState, savedObj, onDataChange } = this.props; + const { timeRange, savedObj, onDataChange } = this.props; this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(savedObj, { vis: {}, timeRange: timeRange, - filters: appState ? appState.filters || [] : [], + filters: [], }); await this._handler.render(this._visEl.current); @@ -287,7 +287,6 @@ VisEditorVisualizationUI.propTypes = { timeRange: PropTypes.object, dirty: PropTypes.bool, autoApply: PropTypes.bool, - appState: PropTypes.object, }; export const VisEditorVisualization = injectI18n(VisEditorVisualizationUI); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js index d0e3acb2ef2fa4..a917a93ffbf5e5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js @@ -44,7 +44,7 @@ import { getDefaultQueryLanguage } from '../../lib/get_default_query_language'; import { QueryBarWrapper } from '../../query_bar_wrapper'; class TableSeriesConfigUI extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; if (!model.color_rules || (model.color_rules && model.color_rules.length === 0)) { this.props.onChange({ 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 925b483905d01c..04a63f60aacf2b 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,12 +18,12 @@ */ import React from 'react'; -import { UiSettingsClientContract, SavedObjectsClientContract } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; export interface ICoreStartContext { appName: string; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; savedObjectsClient: SavedObjectsClientContract; storage: IStorageWrapper; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts index 75a65e131797d7..4d1222d6f5a871 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts @@ -22,7 +22,7 @@ import { CoreStart, Plugin, SavedObjectsClientContract, - UiSettingsClientContract, + IUiSettingsClient, } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; @@ -37,7 +37,7 @@ export interface MetricsPluginSetupDependencies { visualizations: VisualizationsSetup; } export interface MetricsVisualizationDependencies { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; savedObjectsClient: SavedObjectsClientContract; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts index dcc7de4098bddd..af04578b8e27fc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts @@ -17,12 +17,10 @@ * under the License. */ -import { I18nStart, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; +import { I18nStart, SavedObjectsStart, IUiSettingsClient } from 'src/core/public'; import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -export const [getUISettings, setUISettings] = createGetterSetter( - 'UISettings' -); +export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter( 'SavedObjectsClient' diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js index 68b738503c9b3d..3be2e9daed58c6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js @@ -42,7 +42,7 @@ export class Gauge extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js index aa4ac992433971..d66eddae253a15 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js @@ -37,7 +37,7 @@ export class GaugeVis extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js index d6831769c6a6a6..004d59efca3330 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js @@ -37,7 +37,7 @@ export class Metric extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index 9001164afe8207..5166770d1727b8 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -21,7 +21,7 @@ import { CoreSetup, CoreStart, Plugin, - UiSettingsClientContract, + IUiSettingsClient, } from '../../../../core/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; @@ -32,7 +32,7 @@ import { createVegaTypeDefinition } from './vega_type'; /** @internal */ export interface VegaVisualizationDependencies extends LegacyDependenciesPluginSetup { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } /** @internal */ 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 7aa60bb0cc4696..1a5c4713ef7e5a 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 @@ -22,7 +22,6 @@ import { toastNotifications } from 'ui/notify'; 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'; @@ -50,7 +49,7 @@ export const createVegaVisualization = ({ serviceSettings }) => class VegaVisual })); } } else { - idxObj = await data.indexPatterns.indexPatterns.getDefault(); + idxObj = await npStart.plugins.data.indexPatterns.getDefault(); if (!idxObj) { throw new Error(i18n.translate('visTypeVega.visualization.unableToFindDefaultIndexErrorMessage', { defaultMessage: 'Unable to find default index', diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts index 7076cdd8e23e38..ddf1b11945422c 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts +++ b/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts @@ -24,7 +24,7 @@ import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { PersistedState } from 'ui/persisted_state'; import { VisResponseValue } from 'src/plugins/visualizations/public'; import { ExpressionFunction, Render } from 'src/plugins/expressions/public'; -import { start as data } from '../../../data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { start as visualizations } from '../np_ready/public/legacy'; interface Arguments { @@ -92,7 +92,7 @@ export const visualization = (): ExpressionFunctionVisualization => ({ async fn(context, args, handlers) { const $injector = await chrome.dangerouslyGetActiveInjector(); const Private = $injector.get('Private') as any; - const { indexPatterns } = data.indexPatterns; + const indexPatterns = npStart.plugins.data.indexPatterns; const queryFilter = Private(FilterBarQueryFilterProvider); const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts index 63afbca71a2800..434612d11b28aa 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts @@ -17,13 +17,11 @@ * under the License. */ -import { I18nStart, UiSettingsClientContract } from 'src/core/public'; +import { I18nStart, IUiSettingsClient } from 'src/core/public'; import { TypesStart } from './types'; import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; -export const [getUISettings, setUISettings] = createGetterSetter( - 'UISettings' -); +export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); export const [getTypes, setTypes] = createGetterSetter('Types'); diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts index 6e087e3ee7f870..228ef96f8c9f31 100644 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts +++ b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts @@ -17,7 +17,7 @@ * under the License. */ import { Server } from '../../server/kbn_server'; -import { Capabilities } from '../../../core/public'; +import { Capabilities } from '../../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsManagementDefinition } from '../../../core/server/saved_objects/management'; diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index d987260b099bd1..c32418e1aeb62e 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -18,7 +18,7 @@ */ import { Server } from '../server/kbn_server'; -import { Capabilities } from '../../core/public'; +import { Capabilities } from '../../core/server'; // Disable lint errors for imports from src/core/* until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema'; diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.mocks.ts b/src/legacy/server/capabilities/capabilities_mixin.test.mocks.ts deleted file mode 100644 index 64300604b3e9d2..00000000000000 --- a/src/legacy/server/capabilities/capabilities_mixin.test.mocks.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 const mockRegisterCapabilitiesRoute = jest.fn(); -jest.mock('./capabilities_route', () => ({ - registerCapabilitiesRoute: mockRegisterCapabilitiesRoute, -})); diff --git a/src/legacy/server/capabilities/capabilities_mixin.test.ts b/src/legacy/server/capabilities/capabilities_mixin.test.ts index 9b6827e1bb380b..3422d6a8cbb346 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.test.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.test.ts @@ -19,81 +19,47 @@ import { Server } from 'hapi'; import KbnServer from '../kbn_server'; -import { mockRegisterCapabilitiesRoute } from './capabilities_mixin.test.mocks'; import { capabilitiesMixin } from './capabilities_mixin'; describe('capabilitiesMixin', () => { + let registerMock: jest.Mock; + const getKbnServer = (pluginSpecs: any[] = []) => { - return { + return ({ afterPluginsInit: (callback: () => void) => callback(), pluginSpecs, - } as KbnServer; + newPlatform: { + setup: { + core: { + capabilities: { + registerProvider: registerMock, + }, + }, + }, + }, + } as unknown) as KbnServer; }; let server: Server; beforeEach(() => { server = new Server(); + server.getUiNavLinks = () => []; + registerMock = jest.fn(); }); - afterEach(() => { - mockRegisterCapabilitiesRoute.mockClear(); - }); - - it('calls registerCapabilitiesRoute with merged uiCapabilitiesProviers', async () => { - const kbnServer = getKbnServer([ - { - getUiCapabilitiesProvider: () => () => ({ - app1: { read: true }, - management: { section1: { feature1: true } }, - }), - }, - { - getUiCapabilitiesProvider: () => () => ({ - app2: { write: true }, - catalogue: { feature3: true }, - management: { section2: { feature2: true } }, - }), - }, - ]); - - await capabilitiesMixin(kbnServer, server); - - expect(mockRegisterCapabilitiesRoute).toHaveBeenCalledWith( - server, - { - app1: { read: true }, - app2: { write: true }, - catalogue: { feature3: true }, - management: { - section1: { feature1: true }, - section2: { feature2: true }, - }, - navLinks: {}, - }, - [] - ); - }); + it('calls capabilities#registerCapabilitiesProvider for each legacy plugin specs', async () => { + const getPluginSpec = (provider: () => any) => ({ + getUiCapabilitiesProvider: () => provider, + }); - it('exposes server#registerCapabilitiesModifier for providing modifiers to the route', async () => { - const kbnServer = getKbnServer(); + const capaA = { catalogue: { A: true } }; + const capaB = { catalogue: { B: true } }; + const kbnServer = getKbnServer([getPluginSpec(() => capaA), getPluginSpec(() => capaB)]); await capabilitiesMixin(kbnServer, server); - const mockModifier1 = jest.fn(); - const mockModifier2 = jest.fn(); - server.registerCapabilitiesModifier(mockModifier1); - server.registerCapabilitiesModifier(mockModifier2); - - expect(mockRegisterCapabilitiesRoute.mock.calls[0][2]).toEqual([mockModifier1, mockModifier2]); - }); - it('exposes request#getCapabilities for retrieving legacy capabilities', async () => { - const kbnServer = getKbnServer(); - jest.spyOn(server, 'decorate'); - await capabilitiesMixin(kbnServer, server); - expect(server.decorate).toHaveBeenCalledWith( - 'request', - 'getCapabilities', - expect.any(Function) - ); + expect(registerMock).toHaveBeenCalledTimes(2); + expect(registerMock.mock.calls[0][0]()).toEqual(capaA); + expect(registerMock.mock.calls[1][0]()).toEqual(capaB); }); }); diff --git a/src/legacy/server/capabilities/capabilities_mixin.ts b/src/legacy/server/capabilities/capabilities_mixin.ts index b41dfe42c40b24..23a0c35414ae65 100644 --- a/src/legacy/server/capabilities/capabilities_mixin.ts +++ b/src/legacy/server/capabilities/capabilities_mixin.ts @@ -17,51 +17,26 @@ * under the License. */ -import { Server, Request } from 'hapi'; - -import { Capabilities } from '../../../core/public'; +import { Server } from 'hapi'; import KbnServer from '../kbn_server'; -import { registerCapabilitiesRoute } from './capabilities_route'; -import { mergeCapabilities } from './merge_capabilities'; -import { resolveCapabilities } from './resolve_capabilities'; - -export type CapabilitiesModifier = ( - request: Request, - uiCapabilities: Capabilities -) => Capabilities | Promise; export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) { - const modifiers: CapabilitiesModifier[] = []; + const registerLegacyCapabilities = async () => { + const capabilitiesList = await Promise.all( + kbnServer.pluginSpecs + .map(spec => spec.getUiCapabilitiesProvider()) + .filter(provider => !!provider) + .map(provider => provider(server)) + ); - server.decorate('server', 'registerCapabilitiesModifier', (provider: CapabilitiesModifier) => { - modifiers.push(provider); - }); + capabilitiesList.forEach(capabilities => { + kbnServer.newPlatform.setup.core.capabilities.registerProvider(() => capabilities); + }); + }; // Some plugin capabilities are derived from data provided by other plugins, // so we need to wait until after all plugins have been init'd to fetch uiCapabilities. kbnServer.afterPluginsInit(async () => { - const defaultCapabilities = mergeCapabilities( - ...(await Promise.all( - kbnServer.pluginSpecs - .map(spec => spec.getUiCapabilitiesProvider()) - .filter(provider => !!provider) - .map(provider => provider(server)) - )) - ); - - server.decorate('request', 'getCapabilities', function() { - // Get legacy nav links - const navLinks = server.getUiNavLinks().reduce( - (acc, spec) => ({ - ...acc, - [spec._id]: true, - }), - {} as Record - ); - - return resolveCapabilities(this, modifiers, defaultCapabilities, { navLinks }); - }); - - registerCapabilitiesRoute(server, defaultCapabilities, modifiers); + await registerLegacyCapabilities(); }); } diff --git a/src/legacy/server/capabilities/capabilities_route.test.ts b/src/legacy/server/capabilities/capabilities_route.test.ts deleted file mode 100644 index 6f3f706379642d..00000000000000 --- a/src/legacy/server/capabilities/capabilities_route.test.ts +++ /dev/null @@ -1,137 +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 { Server } from 'hapi'; -import { registerCapabilitiesRoute } from './capabilities_route'; -import { Capabilities } from '../../../core/public'; - -describe('capabilities api', () => { - const defaultCapabilities = { - catalogue: { - feature1: true, - feature2: true, - }, - management: { - section1: { - read: true, - }, - section2: { - write: true, - }, - }, - navLinks: { - app1: true, - app2: true, - }, - myApp: { - read: true, - write: true, - kioskMode: true, - }, - } as Capabilities; - - let server: Server; - - beforeEach(() => { - server = new Server(); - }); - - it('returns unmodified uiCapabilities if no modifiers are available', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, []); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - expect(JSON.parse(resp.payload)).toEqual({ - capabilities: defaultCapabilities, - }); - }); - - it('merges payload capabilities with defaultCapabilities', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, []); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: { navLinks: { app3: true } } }, - }); - expect(JSON.parse(resp.payload)).toEqual({ - capabilities: { - ...defaultCapabilities, - navLinks: { - ...defaultCapabilities.navLinks, - app3: true, - }, - }, - }); - }); - - it('allows a single provider to modify uiCapabilities', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, [ - (req, caps) => { - caps.management.section2.write = false; - caps.myApp.write = false; - return caps; - }, - ]); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - const results = JSON.parse(resp.payload); - expect(results.capabilities.management.section2.write).toBe(false); - expect(results.capabilities.myApp.write).toBe(false); - }); - - it('allows multiple providers to modify uiCapabilities', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, [ - (req, caps) => { - caps.management.section2.write = false; - return caps; - }, - (req, caps) => { - caps.myApp.write = false; - return caps; - }, - ]); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - const results = JSON.parse(resp.payload); - expect(results.capabilities.management.section2.write).toBe(false); - expect(results.capabilities.myApp.write).toBe(false); - }); - - it('returns an error if any providers fail', async () => { - registerCapabilitiesRoute(server, defaultCapabilities, [ - (req, caps) => { - throw new Error(`Couldn't fetch license`); - }, - ]); - const resp = await server.inject({ - method: 'POST', - url: '/api/capabilities', - payload: { capabilities: {} }, - }); - expect(resp.statusCode).toBe(500); - }); -}); diff --git a/src/legacy/server/capabilities/capabilities_route.ts b/src/legacy/server/capabilities/capabilities_route.ts deleted file mode 100644 index 5564fbb295a62e..00000000000000 --- a/src/legacy/server/capabilities/capabilities_route.ts +++ /dev/null @@ -1,54 +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 Joi from 'joi'; -import { Server } from 'hapi'; - -import { Capabilities } from '../../../core/public'; -import { CapabilitiesModifier } from './capabilities_mixin'; -import { resolveCapabilities } from './resolve_capabilities'; - -export const registerCapabilitiesRoute = ( - server: Server, - defaultCapabilities: Capabilities, - modifiers: CapabilitiesModifier[] -) => { - server.route({ - path: '/api/capabilities', - method: 'POST', - options: { - validate: { - payload: Joi.object({ - capabilities: Joi.object().required(), - }).required(), - }, - }, - async handler(request) { - const { capabilities } = request.payload as { capabilities: Capabilities }; - return { - capabilities: await resolveCapabilities( - request, - modifiers, - defaultCapabilities, - capabilities - ), - }; - }, - }); -}; diff --git a/src/legacy/server/capabilities/index.ts b/src/legacy/server/capabilities/index.ts index 09461a40c008a3..8c5dea1226f2b6 100644 --- a/src/legacy/server/capabilities/index.ts +++ b/src/legacy/server/capabilities/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { CapabilitiesModifier, capabilitiesMixin } from './capabilities_mixin'; +export { capabilitiesMixin } from './capabilities_mixin'; diff --git a/src/legacy/server/capabilities/merge_capabilities.test.ts b/src/legacy/server/capabilities/merge_capabilities.test.ts deleted file mode 100644 index a73b81bdaf0a32..00000000000000 --- a/src/legacy/server/capabilities/merge_capabilities.test.ts +++ /dev/null @@ -1,84 +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 { mergeCapabilities } from './merge_capabilities'; - -const defaultProps = { - catalogue: {}, - management: {}, - navLinks: {}, -}; - -test(`"{ foo: {} }" doesn't clobber "{ foo: { bar: true } }"`, () => { - const output1 = mergeCapabilities({ foo: { bar: true } }, { foo: {} }); - expect(output1).toEqual({ ...defaultProps, foo: { bar: true } }); - - const output2 = mergeCapabilities({ foo: { bar: true } }, { foo: {} }); - expect(output2).toEqual({ ...defaultProps, foo: { bar: true } }); -}); - -test(`"{ foo: { bar: true } }" doesn't clobber "{ baz: { quz: true } }"`, () => { - const output1 = mergeCapabilities({ foo: { bar: true } }, { baz: { quz: true } }); - expect(output1).toEqual({ ...defaultProps, foo: { bar: true }, baz: { quz: true } }); - - const output2 = mergeCapabilities({ baz: { quz: true } }, { foo: { bar: true } }); - expect(output2).toEqual({ ...defaultProps, foo: { bar: true }, baz: { quz: true } }); -}); - -test(`"{ foo: { bar: { baz: true } } }" doesn't clobber "{ foo: { bar: { quz: true } } }"`, () => { - const output1 = mergeCapabilities( - { foo: { bar: { baz: true } } }, - { foo: { bar: { quz: true } } } - ); - expect(output1).toEqual({ ...defaultProps, foo: { bar: { baz: true, quz: true } } }); - - const output2 = mergeCapabilities( - { foo: { bar: { quz: true } } }, - { foo: { bar: { baz: true } } } - ); - expect(output2).toEqual({ ...defaultProps, foo: { bar: { baz: true, quz: true } } }); -}); - -test(`error is thrown if boolean and object clash`, () => { - expect(() => { - mergeCapabilities({ foo: { bar: { baz: true } } }, { foo: { bar: true } }); - }).toThrowErrorMatchingInlineSnapshot(`"a boolean and an object can't be merged"`); - - expect(() => { - mergeCapabilities({ foo: { bar: true } }, { foo: { bar: { baz: true } } }); - }).toThrowErrorMatchingInlineSnapshot(`"a boolean and an object can't be merged"`); -}); - -test(`supports duplicates as long as the booleans are the same`, () => { - const output1 = mergeCapabilities({ foo: { bar: true } }, { foo: { bar: true } }); - expect(output1).toEqual({ ...defaultProps, foo: { bar: true } }); - - const output2 = mergeCapabilities({ foo: { bar: false } }, { foo: { bar: false } }); - expect(output2).toEqual({ ...defaultProps, foo: { bar: false } }); -}); - -test(`error is thrown if merging "true" and "false"`, () => { - expect(() => { - mergeCapabilities({ foo: { bar: false } }, { foo: { bar: true } }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"true\\" and \\"false\\" can't be merged"`); - - expect(() => { - mergeCapabilities({ foo: { bar: true } }, { foo: { bar: false } }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"true\\" and \\"false\\" can't be merged"`); -}); diff --git a/src/legacy/server/capabilities/merge_capabilities.ts b/src/legacy/server/capabilities/merge_capabilities.ts deleted file mode 100644 index 5fe31775ba32d8..00000000000000 --- a/src/legacy/server/capabilities/merge_capabilities.ts +++ /dev/null @@ -1,44 +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 typeDetect from 'type-detect'; -import { merge } from 'lodash'; -import { Capabilities } from '../../../core/public'; - -export const mergeCapabilities = (...sources: Array>): Capabilities => - merge( - { - navLinks: {}, - management: {}, - catalogue: {}, - }, - ...sources, - (a: any, b: any) => { - if ( - (typeDetect(a) === 'boolean' && typeDetect(b) === 'Object') || - (typeDetect(b) === 'boolean' && typeDetect(a) === 'Object') - ) { - throw new Error(`a boolean and an object can't be merged`); - } - - if (typeDetect(a) === 'boolean' && typeDetect(b) === 'boolean' && a !== b) { - throw new Error(`"true" and "false" can't be merged`); - } - } - ); diff --git a/src/legacy/server/capabilities/resolve_capabilities.ts b/src/legacy/server/capabilities/resolve_capabilities.ts deleted file mode 100644 index 0df4932099b547..00000000000000 --- a/src/legacy/server/capabilities/resolve_capabilities.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 { Request } from 'hapi'; - -import { Capabilities } from '../../../core/public'; -import { mergeCapabilities } from './merge_capabilities'; -import { CapabilitiesModifier } from './capabilities_mixin'; - -export const resolveCapabilities = ( - request: Request, - modifiers: CapabilitiesModifier[], - ...capabilities: Array> -) => - modifiers.reduce( - async (resolvedCaps, modifier) => modifier(request, await resolvedCaps), - Promise.resolve(mergeCapabilities(...capabilities)) - ); diff --git a/src/legacy/server/config/__tests__/deprecation_warnings.js b/src/legacy/server/config/__tests__/deprecation_warnings.js index 0915f7de25b45d..f49a1b6df45e21 100644 --- a/src/legacy/server/config/__tests__/deprecation_warnings.js +++ b/src/legacy/server/config/__tests__/deprecation_warnings.js @@ -26,7 +26,7 @@ const SETUP_NODE_ENV = require.resolve('../../../../setup_node_env'); const SECOND = 1000; describe('config/deprecation warnings', function () { - this.timeout(15 * SECOND); + this.timeout(65 * SECOND); let stdio = ''; let proc = null; @@ -53,7 +53,7 @@ describe('config/deprecation warnings', function () { // Either time out in 60 seconds, or resolve once the line is in our buffer return Promise.race([ - new Promise((resolve) => setTimeout(resolve, 60000)), + new Promise((resolve) => setTimeout(resolve, 60 * SECOND)), new Promise((resolve, reject) => { proc.stdout.on('data', (chunk) => { stdio += chunk.toString('utf8'); diff --git a/src/legacy/server/config/complete.js b/src/legacy/server/config/complete.js index 4100a6b5a9bdff..7dbb3a722e38f3 100644 --- a/src/legacy/server/config/complete.js +++ b/src/legacy/server/config/complete.js @@ -17,91 +17,8 @@ * under the License. */ -import { difference, get, set } from 'lodash'; -import { transformDeprecations } from './transform_deprecations'; -import { unset, formatListAsProse, getFlattenedObject } from '../../utils'; -import { getTransform } from '../../deprecation'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { hasConfigPathIntersection } from '../../../core/server/config/'; - -const getFlattenedKeys = object => Object.keys(getFlattenedObject(object)); - -async function getUnusedConfigKeys( - coreHandledConfigPaths, - plugins, - disabledPluginSpecs, - rawSettings, - configValues -) { - // transform deprecated core settings - const settings = transformDeprecations(rawSettings); - - // transform deprecated plugin settings - for (let i = 0; i < plugins.length; i++) { - const { spec } = plugins[i]; - const transform = await getTransform(spec); - const prefix = spec.getConfigPrefix(); - - // nested plugin prefixes (a.b) translate to nested objects - const pluginSettings = get(settings, prefix); - if (pluginSettings) { - // flattened settings are expected to be converted to nested objects - // a.b = true => { a: { b: true }} - set(settings, prefix, transform(pluginSettings)); - } - } - - // remove config values from disabled plugins - for (const spec of disabledPluginSpecs) { - unset(settings, spec.getConfigPrefix()); - } - - const inputKeys = getFlattenedKeys(settings); - const appliedKeys = getFlattenedKeys(configValues); - - if (inputKeys.includes('env')) { - // env is a special case key, see https://github.com/elastic/kibana/blob/848bf17b/src/legacy/server/config/config.js#L74 - // where it is deleted from the settings before being injected into the schema via context and - // then renamed to `env.name` https://github.com/elastic/kibana/blob/848bf17/src/legacy/server/config/schema.js#L17 - inputKeys[inputKeys.indexOf('env')] = 'env.name'; - } - - // Filter out keys that are marked as used in the core (e.g. by new core plugins). - return difference(inputKeys, appliedKeys).filter( - unusedConfigKey => - !coreHandledConfigPaths.some(usedInCoreConfigKey => - hasConfigPathIntersection(unusedConfigKey, usedInCoreConfigKey) - ) - ); -} - -export default async function (kbnServer, server, config) { +export default function (kbnServer, server) { server.decorate('server', 'config', function () { return kbnServer.config; }); - - const unusedKeys = await getUnusedConfigKeys( - kbnServer.newPlatform.params.handledConfigPaths, - kbnServer.plugins, - kbnServer.disabledPluginSpecs, - kbnServer.settings, - config.get() - ); - - if (!unusedKeys.length) { - return; - } - - const formattedUnusedKeys = unusedKeys.map(key => `"${key}"`); - const desc = formattedUnusedKeys.length === 1 ? 'setting was' : 'settings were'; - - const error = new Error( - `${formatListAsProse(formattedUnusedKeys)} ${desc} not applied. ` + - 'Check for spelling errors and ensure that expected ' + - 'plugins are installed.' - ); - - error.code = 'InvalidConfig'; - error.processExitCode = 64; - throw error; } diff --git a/src/legacy/server/config/complete.test.js b/src/legacy/server/config/complete.test.js index 9d43567e98fcb2..e5484693ae55f6 100644 --- a/src/legacy/server/config/complete.test.js +++ b/src/legacy/server/config/complete.test.js @@ -20,40 +20,28 @@ import completeMixin from './complete'; import sinon from 'sinon'; -/* eslint-disable import/no-duplicates */ -import * as transformDeprecationsNS from './transform_deprecations'; -import { transformDeprecations } from './transform_deprecations'; -/* eslint-enable import/no-duplicates */ - describe('server/config completeMixin()', function () { const sandbox = sinon.createSandbox(); afterEach(() => sandbox.restore()); const setup = (options = {}) => { - const { - settings = {}, - configValues = {}, - disabledPluginSpecs = [], - plugins = [], - } = options; + const { settings = {}, configValues = {}, disabledPluginSpecs = [], plugins = [] } = options; const server = { - decorate: sinon.stub() + decorate: sinon.stub(), }; const config = { - get: sinon.stub().returns(configValues) + get: sinon.stub().returns(configValues), }; const kbnServer = { - newPlatform: { - params: { handledConfigPaths: [] } - }, + newPlatform: {}, settings, server, config, disabledPluginSpecs, - plugins + plugins, }; const callCompleteMixin = () => completeMixin(kbnServer, server, config); @@ -65,7 +53,7 @@ describe('server/config completeMixin()', function () { it('adds a config() function to the server', async () => { const { config, callCompleteMixin, server } = setup({ settings: {}, - configValues: {} + configValues: {}, }); await callCompleteMixin(); @@ -74,243 +62,4 @@ describe('server/config completeMixin()', function () { expect(server.decorate.firstCall.args[2]()).toBe(config); }); }); - - describe('all settings used', () => { - it('should not throw', async function () { - const { callCompleteMixin } = setup({ - settings: { - used: true - }, - configValues: { - used: true - }, - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - - describe('more config values than settings', () => { - it('should not throw', async function () { - const { callCompleteMixin } = setup({ - settings: { - used: true - }, - configValues: { - used: true, - foo: 'bar' - } - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - }); - }); - - describe('env setting specified', () => { - it('should not throw', async () => { - const { callCompleteMixin } = setup({ - settings: { - env: 'development' - }, - configValues: { - env: { - name: 'development' - } - } - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - }); - - describe('settings include non-default array settings', () => { - it('should not throw', async () => { - const { callCompleteMixin } = setup({ - settings: { - foo: [ - 'a', - 'b' - ] - }, - configValues: { - foo: [] - } - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - }); - - describe('some settings unused', () => { - it('should throw an error', async function () { - expect.assertions(1); - const { callCompleteMixin } = setup({ - settings: { - unused: true - }, - configValues: { - used: true - } - }); - try { - await callCompleteMixin(); - } catch(error) { - expect(error.message).toMatch('"unused" setting was not applied'); - } - }); - - describe('error thrown', () => { - it('has correct code, processExitCode, and message', async () => { - expect.assertions(3); - - const { callCompleteMixin } = setup({ - settings: { - unused: true, - foo: 'bar', - namespace: { - with: { - sub: { - keys: true - } - } - } - } - }); - - try { - await callCompleteMixin(); - } catch (error) { - expect(error).toHaveProperty('code', 'InvalidConfig'); - expect(error).toHaveProperty('processExitCode', 64); - expect(error.message).toMatch('"unused", "foo", and "namespace.with.sub.keys"'); - } - }); - }); - }); - - describe('deprecation support', () => { - it('should transform settings when determining what is unused', async function () { - sandbox.spy(transformDeprecationsNS, 'transformDeprecations'); - - const settings = { - foo: 1 - }; - - const { callCompleteMixin } = setup({ - settings, - configValues: { - ...settings - } - }); - - await callCompleteMixin(); - sinon.assert.calledOnce(transformDeprecations); - sinon.assert.calledWithExactly(transformDeprecations, settings); - }); - - it('should use transformed settings when considering what is used', async function () { - sandbox.stub(transformDeprecationsNS, 'transformDeprecations').callsFake((settings) => { - settings.bar = settings.foo; - delete settings.foo; - return settings; - }); - - const { callCompleteMixin } = setup({ - settings: { - foo: 1 - }, - configValues: { - bar: 1 - } - }); - - await callCompleteMixin(); - sinon.assert.calledOnce(transformDeprecations); - }); - - it('should transform deprecated plugin settings', async () => { - const { callCompleteMixin } = setup({ - settings: { - foo: { - foo1: 'bar' - } - }, - configValues: { - foo: { - foo2: 'bar' - } - }, - plugins: [ - { - spec: { - getDeprecationsProvider() { - return async ({ rename }) => [rename('foo1', 'foo2')]; - }, - getConfigPrefix: () => 'foo' - } - } - ], - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - - it('should transform deeply nested deprecated plugin settings', async () => { - const { callCompleteMixin } = setup({ - settings: { - xpack: { - monitoring: { - elasticsearch: { - url: 'http://localhost:9200' - } - } - } - }, - configValues: { - xpack: { - monitoring: { - elasticsearch: { - hosts: 'http://localhost:9200' - } - } - } - }, - plugins: [ - { - spec: { - getDeprecationsProvider() { - return async ({ rename }) => [rename('elasticsearch.url', 'elasticsearch.hosts')]; - }, - getConfigPrefix: () => 'xpack.monitoring' - } - } - ], - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - }); - - describe('disabled plugins', () => { - it('ignores config for plugins that are disabled', async () => { - const { callCompleteMixin } = setup({ - settings: { - foo: { - bar: { - unused: true - } - } - }, - disabledPluginSpecs: [ - { - id: 'foo', - getConfigPrefix: () => 'foo.bar' - } - ], - configValues: {} - }); - - await expect(callCompleteMixin()).resolves.toBe(undefined); - }); - }); }); diff --git a/src/legacy/server/config/config.js b/src/legacy/server/config/config.js index e871a20ee8b981..2fb79d78e780ef 100644 --- a/src/legacy/server/config/config.js +++ b/src/legacy/server/config/config.js @@ -21,9 +21,9 @@ import Joi from 'joi'; import _ from 'lodash'; import override from './override'; import createDefaultSchema from './schema'; -import { getConfig } from '../path'; -import { pkg, unset, deepCloneWithBuffers as clone, IS_KIBANA_DISTRIBUTABLE } from '../../utils'; - +import { unset, deepCloneWithBuffers as clone, IS_KIBANA_DISTRIBUTABLE } from '../../utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { pkg } from '../../../core/server/utils'; const schema = Symbol('Joi Schema'); const schemaExts = Symbol('Schema Extensions'); const vals = Symbol('config values'); @@ -111,7 +111,6 @@ export class Config { buildNum: IS_KIBANA_DISTRIBUTABLE ? pkg.build.number : Number.MAX_SAFE_INTEGER, buildSha: IS_KIBANA_DISTRIBUTABLE ? pkg.build.sha : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', dist: IS_KIBANA_DISTRIBUTABLE, - defaultConfigPath: getConfig(), }; if (!context.dev && !context.prod) { diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 3f9b897730f51c..a19a39da0f6dd9 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -20,9 +20,8 @@ import Joi from 'joi'; import os from 'os'; import { join } from 'path'; -import { - getData -} from '../path'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getDataPath } from '../../../core/server/path'; // Still used by optimize config schema import { DEFAULT_CSP_RULES, DEFAULT_CSP_STRICT, @@ -106,6 +105,7 @@ export default () => Joi.object({ maxPayloadBytes: HANDLED_IN_NEW_PLATFORM, socketTimeout: HANDLED_IN_NEW_PLATFORM, ssl: HANDLED_IN_NEW_PLATFORM, + compression: HANDLED_IN_NEW_PLATFORM, }).default(), uiSettings: HANDLED_IN_NEW_PLATFORM, @@ -135,7 +135,14 @@ export default () => Joi.object({ then: Joi.default(!process.stdout.isTTY), otherwise: Joi.default(true) }), - timezone: Joi.string() + timezone: Joi.string(), + rotate: Joi.object().keys({ + enabled: Joi.boolean().default(false), + everyBytes: Joi.number().greater(1024).default(10485760), + keepFiles: Joi.number().greater(2).less(1024).default(7), + pollingInterval: Joi.number().greater(5000).less(3600000).default(10000), + usePolling: Joi.boolean().default(false) + }).default() }).default(), ops: Joi.object({ @@ -148,9 +155,7 @@ export default () => Joi.object({ initialize: Joi.boolean().default(true) }).default(), - path: Joi.object({ - data: Joi.string().default(getData()) - }).default(), + path: HANDLED_IN_NEW_PLATFORM, stats: Joi.object({ maximumWaitTimeForAllCollectorsInS: Joi.number().default(60) @@ -159,7 +164,7 @@ export default () => Joi.object({ optimize: Joi.object({ enabled: Joi.boolean().default(true), bundleFilter: Joi.string().default('!tests'), - bundleDir: Joi.string().default(join(getData(), 'optimize')), + bundleDir: Joi.string().default(join(getDataPath(), 'optimize')), viewCaching: Joi.boolean().default(Joi.ref('$prod')), watch: Joi.boolean().default(false), watchPort: Joi.number().default(5602), diff --git a/src/legacy/server/csp/index.test.ts b/src/legacy/server/csp/index.test.ts index 207b7be9df26ab..fbb63bd49bf6fe 100644 --- a/src/legacy/server/csp/index.test.ts +++ b/src/legacy/server/csp/index.test.ts @@ -40,8 +40,7 @@ test('default CSP rules', () => { expect(DEFAULT_CSP_RULES).toMatchInlineSnapshot(` Array [ "script-src 'unsafe-eval' 'self'", - "worker-src blob:", - "child-src blob:", + "worker-src blob: 'self'", "style-src 'unsafe-inline' 'self'", ] `); diff --git a/src/legacy/server/csp/index.ts b/src/legacy/server/csp/index.ts index 476997dc48d667..ae5cb63ad6ff82 100644 --- a/src/legacy/server/csp/index.ts +++ b/src/legacy/server/csp/index.ts @@ -19,8 +19,7 @@ export const DEFAULT_CSP_RULES = Object.freeze([ `script-src 'unsafe-eval' 'self'`, - 'worker-src blob:', - 'child-src blob:', + `worker-src blob: 'self'`, `style-src 'unsafe-inline' 'self'`, ]); diff --git a/src/legacy/server/http/integration_tests/default_route_provider_config.test.ts b/src/legacy/server/http/integration_tests/default_route_provider_config.test.ts new file mode 100644 index 00000000000000..da785a59893ab6 --- /dev/null +++ b/src/legacy/server/http/integration_tests/default_route_provider_config.test.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 * as kbnTestServer from '../../../../test_utils/kbn_server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { Root } from '../../../../core/server/root'; + +describe('default route provider', () => { + let root: Root; + + afterEach(async () => await root.shutdown()); + + it('redirects to the configured default route', async function() { + root = kbnTestServer.createRoot({ + server: { + defaultRoute: '/app/some/default/route', + }, + }); + + await root.setup(); + await root.start(); + + const kbnServer = kbnTestServer.getKbnServer(root); + + kbnServer.server.decorate('request', 'getSavedObjectsClient', function() { + return { + get: (type: string, id: string) => ({ attributes: {} }), + }; + }); + + const { status, header } = await kbnTestServer.request.get(root, '/'); + + expect(status).toEqual(302); + expect(header).toMatchObject({ + location: '/app/some/default/route', + }); + }); +}); diff --git a/src/legacy/server/i18n/index.js b/src/legacy/server/i18n/index.js index ce01d07576f28c..50261d3988f6e1 100644 --- a/src/legacy/server/i18n/index.js +++ b/src/legacy/server/i18n/index.js @@ -19,7 +19,8 @@ import { i18n, i18nLoader } from '@kbn/i18n'; import { basename } from 'path'; -import { fromRoot } from '../../utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { fromRoot } from '../../../core/server/utils'; import { getTranslationPaths } from './get_translations_path'; import { I18N_RC } from './constants'; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 6f2730476956ee..d6dea4decee007 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -20,7 +20,7 @@ import { ResponseObject, Server } from 'hapi'; import { UnwrapPromise } from '@kbn/utility-types'; -import { SavedObjectsClientProviderOptions, CoreSetup } from 'src/core/server'; +import { SavedObjectsClientProviderOptions, CoreSetup, CoreStart } from 'src/core/server'; import { ConfigService, ElasticsearchServiceSetup, @@ -36,18 +36,16 @@ import { LegacyServiceSetupDeps, LegacyServiceStartDeps } from '../../core/serve // Disable lint errors for imports from src/core/server/saved_objects until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsManagement } from '../../core/server/saved_objects/management'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { LegacyConfig } from '../../core/server/legacy'; import { ApmOssPlugin } from '../core_plugins/apm_oss'; import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; import { UsageCollectionSetup } from '../../plugins/usage_collection/server'; -import { CapabilitiesModifier } from './capabilities'; import { IndexPatternsServiceFactory } from './index_patterns'; -import { Capabilities } from '../../core/public'; +import { Capabilities } from '../../core/server'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; -export interface KibanaConfig { - get(key: string): T; - has(key: string): boolean; -} +export type KibanaConfig = LegacyConfig; export interface UiApp { getId(): string; @@ -69,7 +67,6 @@ declare module 'hapi' { savedObjects: SavedObjectsLegacyService; injectUiAppVars: (pluginName: string, getAppVars: () => { [key: string]: any }) => void; getHiddenUiAppById(appId: string): UiApp; - registerCapabilitiesModifier: (provider: CapabilitiesModifier) => void; addScopedTutorialContextFactory: ( scopedTutorialContextFactory: (...args: any[]) => any ) => void; @@ -90,7 +87,6 @@ declare module 'hapi' { getBasePath(): string; getDefaultRoute(): Promise; getUiSettingsService(): IUiSettingsClient; - getCapabilities(): Promise; } interface ResponseToolkit { @@ -127,13 +123,10 @@ export default class KbnServer { plugins: PluginsSetup; }; start: { - core: CoreSetup; + core: CoreStart; plugins: Record; }; stop: null; - params: { - handledConfigPaths: UnwrapPromise>; - }; }; public server: Server; public inject: Server['inject']; diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index e5f182c931d80a..87fb9dc8b9fecb 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -21,7 +21,8 @@ import { constant, once, compact, flatten } from 'lodash'; import { isWorker } from 'cluster'; -import { fromRoot, pkg } from '../utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { fromRoot, pkg } from '../../core/server/utils'; import { Config } from './config'; import loggingConfiguration from './logging/configuration'; import httpMixin from './http'; @@ -56,7 +57,7 @@ export default class KbnServer { this.settings = settings || {}; this.config = config; - const { setupDeps, startDeps, handledConfigPaths, logger, __internals, env } = core; + const { setupDeps, startDeps, logger, __internals, env } = core; this.server = __internals.hapiServer; this.newPlatform = { @@ -71,9 +72,6 @@ export default class KbnServer { setup: setupDeps, start: startDeps, stop: null, - params: { - handledConfigPaths, - }, }; this.uiExports = legacyPlugins.uiExports; diff --git a/src/legacy/server/logging/index.js b/src/legacy/server/logging/index.js index 6e07757abf7bc4..defbcd3331df49 100644 --- a/src/legacy/server/logging/index.js +++ b/src/legacy/server/logging/index.js @@ -20,6 +20,7 @@ import good from '@elastic/good'; import loggingConfiguration from './configuration'; import { logWithMetadata } from './log_with_metadata'; +import { setupLoggingRotate } from './rotate'; export async function setupLogging(server, config) { return await server.register({ @@ -30,5 +31,7 @@ export async function setupLogging(server, config) { export async function loggingMixin(kbnServer, server, config) { logWithMetadata.decorateServer(server); - return await setupLogging(server, config); + + await setupLogging(server, config); + await setupLoggingRotate(server, config); } diff --git a/src/legacy/server/logging/rotate/index.ts b/src/legacy/server/logging/rotate/index.ts new file mode 100644 index 00000000000000..646c89efe8e20e --- /dev/null +++ b/src/legacy/server/logging/rotate/index.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 { isMaster, isWorker } from 'cluster'; +import { Server } from 'hapi'; +import { LogRotator } from './log_rotator'; +import { KibanaConfig } from '../../kbn_server'; + +let logRotator: LogRotator; + +export async function setupLoggingRotate(server: Server, config: KibanaConfig) { + // If log rotate is not enabled we skip + if (!config.get('logging.rotate.enabled')) { + return; + } + + // We just want to start the logging rotate service once + // and we choose to use the master (prod) or the worker server (dev) + if (!isMaster && isWorker && process.env.kbnWorkerType !== 'server') { + return; + } + + // We don't want to run logging rotate server if + // we are not logging to a file + if (config.get('logging.dest') === 'stdout') { + server.log( + ['warning', 'logging:rotate'], + 'Log rotation is enabled but logging.dest is configured for stdout. Set logging.dest to a file for this setting to take effect.' + ); + return; + } + + // Enable Logging Rotate Service + // We need the master process and it can + // try to setupLoggingRotate more than once, + // so we'll need to assure it only loads once. + if (!logRotator) { + logRotator = new LogRotator(config, server); + await logRotator.start(); + } + + return logRotator; +} diff --git a/src/legacy/server/logging/rotate/log_rotator.test.ts b/src/legacy/server/logging/rotate/log_rotator.test.ts new file mode 100644 index 00000000000000..c2100546364d41 --- /dev/null +++ b/src/legacy/server/logging/rotate/log_rotator.test.ts @@ -0,0 +1,265 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import del from 'del'; +import fs, { existsSync, mkdirSync, statSync, writeFileSync } from 'fs'; +import { LogRotator } from './log_rotator'; +import { tmpdir } from 'os'; +import { dirname, join } from 'path'; + +const mockOn = jest.fn(); +jest.mock('chokidar', () => ({ + watch: jest.fn(() => ({ + on: mockOn, + close: jest.fn(), + })), +})); + +jest.mock('lodash', () => ({ + ...require.requireActual('lodash'), + throttle: (fn: any) => fn, +})); + +const tempDir = join(tmpdir(), 'kbn_log_rotator_test'); +const testFilePath = join(tempDir, 'log_rotator_test_log_file.log'); + +const createLogRotatorConfig: any = (logFilePath: string) => { + return new Map([ + ['logging.dest', logFilePath], + ['logging.rotate.everyBytes', 2], + ['logging.rotate.keepFiles', 2], + ['logging.rotate.usePolling', false], + ['logging.rotate.pollingInterval', 10000], + ] as any); +}; + +const mockServer: any = { + log: jest.fn(), +}; + +const writeBytesToFile = (filePath: string, numberOfBytes: number) => { + writeFileSync(filePath, 'a'.repeat(numberOfBytes), { flag: 'a' }); +}; + +describe('LogRotator', () => { + beforeEach(() => { + mkdirSync(tempDir, { recursive: true }); + writeFileSync(testFilePath, ''); + }); + + afterEach(() => { + del.sync(dirname(testFilePath), { force: true }); + mockOn.mockClear(); + }); + + it('rotates log file when bigger than set limit on start', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + await logRotator.stop(); + const testLogFileDir = dirname(testFilePath); + + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file when equal than set limit over time', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeFalsy(); + + writeBytesToFile(testFilePath, 1); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file when file size is bigger than limit', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeFalsy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 3 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file service correctly keeps number of files', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.2'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + }); + + it('rotates log file service correctly keeps number of files even when number setting changes', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.2'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + + logRotator.keepFiles = 1; + await logRotator.start(); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + }); + + it('rotates log file service correctly detects usePolling when it should be false', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(false); + + const usePolling = await logRotator._shouldUsePolling(); + expect(usePolling).toBe(false); + + await logRotator.stop(); + }); + + it('rotates log file service correctly detects usePolling when it should be true', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + + jest.spyOn(fs, 'watch').mockImplementation( + () => + ({ + on: jest.fn((eventType, cb) => { + if (eventType === 'error') { + cb(); + } + }), + close: jest.fn(), + } as any) + ); + + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(true); + + await logRotator.stop(); + }); + + it('rotates log file service correctly fallback to usePolling true after defined timeout', async () => { + jest.useFakeTimers(); + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + jest.spyOn(fs, 'watch').mockImplementation( + () => + ({ + on: jest.fn((ev: string) => { + if (ev === 'error') { + jest.runTimersToTime(15000); + } + }), + close: jest.fn(), + } as any) + ); + + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(true); + + await logRotator.stop(); + jest.useRealTimers(); + }); +}); diff --git a/src/legacy/server/logging/rotate/log_rotator.ts b/src/legacy/server/logging/rotate/log_rotator.ts new file mode 100644 index 00000000000000..3662910ca5a7ba --- /dev/null +++ b/src/legacy/server/logging/rotate/log_rotator.ts @@ -0,0 +1,359 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 chokidar from 'chokidar'; +import { isMaster } from 'cluster'; +import fs from 'fs'; +import { Server } from 'hapi'; +import { throttle } from 'lodash'; +import { tmpdir } from 'os'; +import { basename, dirname, join, sep } from 'path'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { promisify } from 'util'; +import { KibanaConfig } from '../../kbn_server'; + +const mkdirAsync = promisify(fs.mkdir); +const readdirAsync = promisify(fs.readdir); +const renameAsync = promisify(fs.rename); +const statAsync = promisify(fs.stat); +const unlinkAsync = promisify(fs.unlink); +const writeFileAsync = promisify(fs.writeFile); + +export class LogRotator { + private readonly config: KibanaConfig; + private readonly log: Server['log']; + public logFilePath: string; + public everyBytes: number; + public keepFiles: number; + public running: boolean; + private logFileSize: number; + public isRotating: boolean; + public throttledRotate: () => void; + public stalker: chokidar.FSWatcher | null; + public usePolling: boolean; + public pollingInterval: number; + private stalkerUsePollingPolicyTestTimeout: NodeJS.Timeout | null; + + constructor(config: KibanaConfig, server: Server) { + this.config = config; + this.log = server.log.bind(server); + this.logFilePath = config.get('logging.dest'); + this.everyBytes = config.get('logging.rotate.everyBytes'); + this.keepFiles = config.get('logging.rotate.keepFiles'); + this.running = false; + this.logFileSize = 0; + this.isRotating = false; + this.throttledRotate = throttle(async () => await this._rotate(), 5000); + this.stalker = null; + this.usePolling = config.get('logging.rotate.usePolling'); + this.pollingInterval = config.get('logging.rotate.pollingInterval'); + this.stalkerUsePollingPolicyTestTimeout = null; + } + + async start() { + if (this.running) { + return; + } + + this.running = true; + + // create exit listener for cleanup purposes + this._createExitListener(); + + // call rotate on startup + await this._callRotateOnStartup(); + + // init log file size monitor + await this._startLogFileSizeMonitor(); + } + + stop = () => { + if (!this.running) { + return; + } + + // cleanup exit listener + this._deleteExitListener(); + + // stop log file size monitor + this._stopLogFileSizeMonitor(); + + this.running = false; + }; + + async _shouldUsePolling() { + try { + // Setup a test file in order to try the fs env + // and understand if we need to usePolling or not + const tempFileDir = tmpdir(); + const tempFile = join(tempFileDir, 'kbn_log_rotation_use_polling_test_file.log'); + + await mkdirAsync(tempFileDir, { recursive: true }); + await writeFileAsync(tempFile, ''); + + // setup fs.watch for the temp test file + const testWatcher = fs.watch(tempFile, { persistent: false }); + + // await writeFileAsync(tempFile, 'test'); + + const usePollingTest$ = new Observable(observer => { + // observable complete function + const completeFn = (completeStatus: boolean) => { + if (this.stalkerUsePollingPolicyTestTimeout) { + clearTimeout(this.stalkerUsePollingPolicyTestTimeout); + } + testWatcher.close(); + + observer.next(completeStatus); + observer.complete(); + }; + + // setup conditions that would fire the observable + this.stalkerUsePollingPolicyTestTimeout = setTimeout(() => completeFn(true), 15000); + testWatcher.on('change', () => completeFn(false)); + testWatcher.on('error', () => completeFn(true)); + + // fire test watcher events + setTimeout(() => { + fs.writeFileSync(tempFile, 'test'); + }, 0); + }); + + // wait for the first observable result and consider it as the result + // for our use polling test + const usePollingTestResult = await usePollingTest$.pipe(first()).toPromise(); + + // delete the temp file used for the test + await unlinkAsync(tempFile); + + return usePollingTestResult; + } catch { + return true; + } + } + + async _startLogFileSizeMonitor() { + this.usePolling = await this._shouldUsePolling(); + + if (this.usePolling && this.usePolling !== this.config.get('logging.rotate.usePolling')) { + this.log( + ['warning', 'logging:rotate'], + 'The current environment does not support `fs.watch`. Falling back to polling using `fs.watchFile`' + ); + } + + this.stalker = chokidar.watch(this.logFilePath, { + ignoreInitial: true, + awaitWriteFinish: false, + useFsEvents: false, + usePolling: this.usePolling, + interval: this.pollingInterval, + binaryInterval: this.pollingInterval, + alwaysStat: true, + atomic: false, + }); + this.stalker.on('change', this._logFileSizeMonitorHandler); + } + + _logFileSizeMonitorHandler = async (filename: string, stats: fs.Stats) => { + if (!filename || !stats) { + return; + } + + this.logFileSize = stats.size || 0; + await this.throttledRotate(); + }; + + _stopLogFileSizeMonitor() { + if (!this.stalker) { + return; + } + + this.stalker.close(); + + if (this.stalkerUsePollingPolicyTestTimeout) { + clearTimeout(this.stalkerUsePollingPolicyTestTimeout); + } + } + + _createExitListener() { + process.on('exit', this.stop); + } + + _deleteExitListener() { + process.removeListener('exit', this.stop); + } + + async _getLogFileSizeAndCreateIfNeeded() { + try { + const logFileStats = await statAsync(this.logFilePath); + return logFileStats.size; + } catch { + // touch the file to make the watcher being able to register + // change events + await writeFileAsync(this.logFilePath, ''); + return 0; + } + } + + async _callRotateOnStartup() { + this.logFileSize = await this._getLogFileSizeAndCreateIfNeeded(); + await this._rotate(); + } + + _shouldRotate() { + // should rotate evaluation + // 1. should rotate if current log size exceeds + // the defined one on everyBytes + // 2. should not rotate if is already rotating or if any + // of the conditions on 1. do not apply + if (this.isRotating) { + return false; + } + + return this.logFileSize >= this.everyBytes; + } + + async _rotate() { + if (!this._shouldRotate()) { + return; + } + + await this._rotateNow(); + } + + async _rotateNow() { + // rotate process + // 1. get rotated files metadata (list of log rotated files present on the log folder, numerical sorted) + // 2. delete last file + // 3. rename all files to the correct index +1 + // 4. rename + compress current log into 1 + // 5. send SIGHUP to reload log config + + // rotate process is starting + this.isRotating = true; + + // get rotated files metadata + const foundRotatedFiles = await this._readRotatedFilesMetadata(); + + // delete number of rotated files exceeding the keepFiles limit setting + const rotatedFiles: string[] = await this._deleteFoundRotatedFilesAboveKeepFilesLimit( + foundRotatedFiles + ); + + // delete last file + await this._deleteLastRotatedFile(rotatedFiles); + + // rename all files to correct index + 1 + // and normalize numbering if by some reason + // (for example log file deletion) that numbering + // was interrupted + await this._renameRotatedFilesByOne(rotatedFiles); + + // rename current log into 0 + await this._rotateCurrentLogFile(); + + // send SIGHUP to reload log configuration + this._sendReloadLogConfigSignal(); + + // Reset log file size + this.logFileSize = 0; + + // rotate process is finished + this.isRotating = false; + } + + async _readRotatedFilesMetadata() { + const logFileBaseName = basename(this.logFilePath); + const logFilesFolder = dirname(this.logFilePath); + const foundLogFiles: string[] = await readdirAsync(logFilesFolder); + + return ( + foundLogFiles + .filter(file => new RegExp(`${logFileBaseName}\\.\\d`).test(file)) + // we use .slice(-1) here in order to retrieve the last number match in the read filenames + .sort((a, b) => Number(a.match(/(\d+)/g)!.slice(-1)) - Number(b.match(/(\d+)/g)!.slice(-1))) + .map(filename => `${logFilesFolder}${sep}${filename}`) + ); + } + + async _deleteFoundRotatedFilesAboveKeepFilesLimit(foundRotatedFiles: string[]) { + if (foundRotatedFiles.length <= this.keepFiles) { + return foundRotatedFiles; + } + + const finalRotatedFiles = foundRotatedFiles.slice(0, this.keepFiles); + const rotatedFilesToDelete = foundRotatedFiles.slice( + finalRotatedFiles.length, + foundRotatedFiles.length + ); + + await Promise.all( + rotatedFilesToDelete.map((rotatedFilePath: string) => unlinkAsync(rotatedFilePath)) + ); + + return finalRotatedFiles; + } + + async _deleteLastRotatedFile(rotatedFiles: string[]) { + if (rotatedFiles.length < this.keepFiles) { + return; + } + + const lastFilePath: string = rotatedFiles.pop() as string; + await unlinkAsync(lastFilePath); + } + + async _renameRotatedFilesByOne(rotatedFiles: string[]) { + const logFileBaseName = basename(this.logFilePath); + const logFilesFolder = dirname(this.logFilePath); + + for (let i = rotatedFiles.length - 1; i >= 0; i--) { + const oldFilePath = rotatedFiles[i]; + const newFilePath = `${logFilesFolder}${sep}${logFileBaseName}.${i + 1}`; + await renameAsync(oldFilePath, newFilePath); + } + } + + async _rotateCurrentLogFile() { + const newFilePath = `${this.logFilePath}.0`; + await renameAsync(this.logFilePath, newFilePath); + } + + _sendReloadLogConfigSignal() { + if (isMaster) { + (process as NodeJS.EventEmitter).emit('SIGHUP'); + return; + } + + // Send a special message to the cluster manager + // so it can forward it correctly + // It will only run when we are under cluster mode (not under a production environment) + if (!process.send) { + this.log( + ['error', 'logging:rotate'], + 'For some unknown reason process.send is not defined, the rotation was not successful' + ); + return; + } + + process.send(['RELOAD_LOGGING_CONFIG_FROM_SERVER_WORKER']); + } +} diff --git a/src/legacy/server/path/index.js b/src/legacy/server/path/index.js deleted file mode 100644 index 130ce59694d072..00000000000000 --- a/src/legacy/server/path/index.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 { join } from 'path'; -import { accessSync, R_OK } from 'fs'; -import { find } from 'lodash'; -import { fromRoot } from '../../utils'; - -const CONFIG_PATHS = [ - process.env.KIBANA_PATH_CONF && join(process.env.KIBANA_PATH_CONF, 'kibana.yml'), - process.env.CONFIG_PATH, //deprecated - fromRoot('config/kibana.yml'), - '/etc/kibana/kibana.yml' -].filter(Boolean); - -const DATA_PATHS = [ - process.env.DATA_PATH, //deprecated - fromRoot('data'), - '/var/lib/kibana' -].filter(Boolean); - -function findFile(paths) { - const availablePath = find(paths, configPath => { - try { - accessSync(configPath, R_OK); - return true; - } catch (e) { - //Check the next path - } - }); - return availablePath || paths[0]; -} - -export const getConfig = () => findFile(CONFIG_PATHS); -export const getData = () => findFile(DATA_PATHS); diff --git a/src/legacy/server/path/index.test.js b/src/legacy/server/path/index.test.js deleted file mode 100644 index 08d3568cfbbadb..00000000000000 --- a/src/legacy/server/path/index.test.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 { getConfig, getData } from './'; -import { accessSync, R_OK } from 'fs'; - -describe('Default path finder', function () { - it('should find a kibana.yml', () => { - const configPath = getConfig(); - expect(() => accessSync(configPath, R_OK)).not.toThrow(); - }); - - it('should find a data directory', () => { - const dataPath = getData(); - expect(() => accessSync(dataPath, R_OK)).not.toThrow(); - }); -}); diff --git a/src/legacy/server/sass/build.js b/src/legacy/server/sass/build.js index db1b26e1028348..2872ef5daa2004 100644 --- a/src/legacy/server/sass/build.js +++ b/src/legacy/server/sass/build.js @@ -30,7 +30,7 @@ import { PUBLIC_PATH_PLACEHOLDER } from '../../../optimize/public_path_placehold const renderSass = promisify(sass.render); const writeFile = promisify(fs.writeFile); -const exists = promisify(fs.exists); +const access = promisify(fs.access); const copyFile = promisify(fs.copyFile); const mkdirAsync = promisify(fs.mkdir); @@ -145,23 +145,27 @@ export class Build { ]; // verify that asset sources exist and import is valid before writing anything - await Promise.all(urlAssets.map(async (asset) => { - if (!await exists(asset.path)) { - throw this._makeError( - 'Invalid url() in css output', - `url("${asset.requestUrl}") resolves to "${asset.path}", which does not exist.\n` + - ` Make sure that the request is relative to "${asset.root}"` - ); - } + await Promise.all( + urlAssets.map(async asset => { + try { + await access(asset.path); + } catch (e) { + throw this._makeError( + 'Invalid url() in css output', + `url("${asset.requestUrl}") resolves to "${asset.path}", which does not exist.\n` + + ` Make sure that the request is relative to "${asset.root}"` + ); + } - if (!isPathInside(asset.path, asset.boundry)) { - throw this._makeError( - 'Invalid url() in css output', - `url("${asset.requestUrl}") resolves to "${asset.path}"\n` + - ` which is outside of "${asset.boundry}"` - ); - } - })); + if (!isPathInside(asset.path, asset.boundry)) { + throw this._makeError( + 'Invalid url() in css output', + `url("${asset.requestUrl}") resolves to "${asset.path}"\n` + + ` which is outside of "${asset.boundry}"` + ); + } + }) + ); // write css await mkdirAsync(this.targetDir, { recursive: true }); diff --git a/src/legacy/server/sass/build.test.js b/src/legacy/server/sass/build.test.js index 810b7cb44b9171..bde552e9df00ff 100644 --- a/src/legacy/server/sass/build.test.js +++ b/src/legacy/server/sass/build.test.js @@ -47,14 +47,35 @@ it('builds light themed SASS', async () => { expect(readFileSync(targetPath, 'utf8').replace(/(\/\*# sourceMappingURL=).*( \*\/)/, '$1...$2')) .toMatchInlineSnapshot(` -"foo bar { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - background: #e6f0f8 url(./images/img.png) url(ui/assets/favicons/favicon.ico); } -/*# sourceMappingURL=... */" -`); + "/* 1 */ + /* 1 */ + /** + * 1. Extend beta badges to at least 40% of the container's width + * 2. Fix for IE to ensure badges are visible outside of a ` - ); - // If the collapsable element has an id, also set aria-controls - if ($elem.attr('id')) { - $collapser.attr('aria-controls', $elem.attr('id')); - } - const $icon = $(''); - $collapser.append($icon); - const $siblings = $elem.siblings(); + ); + // If the collapsable element has an id, also set aria-controls + if ($elem.attr('id')) { + $collapser.attr('aria-controls', $elem.attr('id')); + } + const $icon = $(''); + $collapser.append($icon); + const $siblings = $elem.siblings(); - const siblingsClass = listOfWidthClasses.reduce(function (prev, className) { - if (prev) return prev; - return $siblings.hasClass(className) && className; - }, false); + const siblingsClass = listOfWidthClasses.reduce(function (prev, className) { + if (prev) return prev; + return $siblings.hasClass(className) && className; + }, false); - // If there is are only two elements we can assume the other one will take 100% of the width. - const hasSingleSibling = $siblings.length === 1 && siblingsClass; + // If there is are only two elements we can assume the other one will take 100% of the width. + const hasSingleSibling = $siblings.length === 1 && siblingsClass; - $collapser.on('click', function () { - if (isCollapsed) { - isCollapsed = false; - $elem.removeClass('closed'); - $icon.addClass('fa-chevron-circle-left'); - $icon.removeClass('fa-chevron-circle-right'); - $collapser.attr('aria-expanded', 'true'); - } else { - isCollapsed = true; - $elem.addClass('closed'); - $icon.removeClass('fa-chevron-circle-left'); - $icon.addClass('fa-chevron-circle-right'); - $collapser.attr('aria-expanded', 'false'); - } + $collapser.on('click', function () { + if (isCollapsed) { + isCollapsed = false; + $elem.removeClass('closed'); + $icon.addClass('fa-chevron-circle-left'); + $icon.removeClass('fa-chevron-circle-right'); + $collapser.attr('aria-expanded', 'true'); + } else { + isCollapsed = true; + $elem.addClass('closed'); + $icon.removeClass('fa-chevron-circle-left'); + $icon.addClass('fa-chevron-circle-right'); + $collapser.attr('aria-expanded', 'false'); + } - if (hasSingleSibling) { - $siblings.toggleClass(siblingsClass + ' col-md-12'); - } + if (hasSingleSibling) { + $siblings.toggleClass(siblingsClass + ' col-md-12'); + } - if ($scope.toggleSidebar) $scope.toggleSidebar(); - }); + if ($scope.toggleSidebar) $scope.toggleSidebar(); + }); - $collapser.appendTo($elem); - } - }; - }); + $collapser.appendTo($elem); + }, + }; +} + +uiModules.get('kibana').directive('collapsibleSidebar', CollapsibleSidebarProvider); diff --git a/src/legacy/ui/public/courier/fetch/fetch_soon.test.ts b/src/legacy/ui/public/courier/fetch/fetch_soon.test.ts index e753c526b748d9..d96fb536985da0 100644 --- a/src/legacy/ui/public/courier/fetch/fetch_soon.test.ts +++ b/src/legacy/ui/public/courier/fetch/fetch_soon.test.ts @@ -19,14 +19,14 @@ import { fetchSoon } from './fetch_soon'; import { callClient } from './call_client'; -import { UiSettingsClientContract } from '../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../core/public'; import { FetchHandlers, FetchOptions } from './types'; import { SearchRequest, SearchResponse } from '../types'; function getConfigStub(config: any = {}) { return { get: key => config[key], - } as UiSettingsClientContract; + } as IUiSettingsClient; } const mockResponses: Record = { diff --git a/src/legacy/ui/public/courier/fetch/get_search_params.test.ts b/src/legacy/ui/public/courier/fetch/get_search_params.test.ts index d6f3d33099599e..76f3105d7f942e 100644 --- a/src/legacy/ui/public/courier/fetch/get_search_params.test.ts +++ b/src/legacy/ui/public/courier/fetch/get_search_params.test.ts @@ -18,12 +18,12 @@ */ import { getMSearchParams, getSearchParams } from './get_search_params'; -import { UiSettingsClientContract } from '../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../core/public'; function getConfigStub(config: any = {}) { return { get: key => config[key], - } as UiSettingsClientContract; + } as IUiSettingsClient; } describe('getMSearchParams', () => { diff --git a/src/legacy/ui/public/courier/fetch/get_search_params.ts b/src/legacy/ui/public/courier/fetch/get_search_params.ts index 6b8da07ca93d4f..9fb8f2c728c6f0 100644 --- a/src/legacy/ui/public/courier/fetch/get_search_params.ts +++ b/src/legacy/ui/public/courier/fetch/get_search_params.ts @@ -17,11 +17,11 @@ * under the License. */ -import { UiSettingsClientContract } from '../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../core/public'; const sessionId = Date.now(); -export function getMSearchParams(config: UiSettingsClientContract) { +export function getMSearchParams(config: IUiSettingsClient) { return { rest_total_hits_as_int: true, ignore_throttled: getIgnoreThrottled(config), @@ -29,7 +29,7 @@ export function getMSearchParams(config: UiSettingsClientContract) { }; } -export function getSearchParams(config: UiSettingsClientContract, esShardTimeout: number = 0) { +export function getSearchParams(config: IUiSettingsClient, esShardTimeout: number = 0) { return { rest_total_hits_as_int: true, ignore_unavailable: true, @@ -40,16 +40,16 @@ export function getSearchParams(config: UiSettingsClientContract, esShardTimeout }; } -export function getIgnoreThrottled(config: UiSettingsClientContract) { +function getIgnoreThrottled(config: IUiSettingsClient) { return !config.get('search:includeFrozen'); } -export function getMaxConcurrentShardRequests(config: UiSettingsClientContract) { +function getMaxConcurrentShardRequests(config: IUiSettingsClient) { const maxConcurrentShardRequests = config.get('courier:maxConcurrentShardRequests'); return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined; } -export function getPreference(config: UiSettingsClientContract) { +export function getPreference(config: IUiSettingsClient) { const setRequestPreference = config.get('courier:setRequestPreference'); if (setRequestPreference === 'sessionId') return sessionId; return setRequestPreference === 'custom' diff --git a/src/legacy/ui/public/courier/fetch/types.ts b/src/legacy/ui/public/courier/fetch/types.ts index e341e1ab35c5cb..03bf51ae15d453 100644 --- a/src/legacy/ui/public/courier/fetch/types.ts +++ b/src/legacy/ui/public/courier/fetch/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiSettingsClientContract } from '../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../core/public'; import { SearchRequest, SearchResponse } from '../types'; export interface ApiCaller { @@ -36,6 +36,6 @@ export interface FetchOptions { export interface FetchHandlers { es: ApiCaller; - config: UiSettingsClientContract; + config: IUiSettingsClient; esShardTimeout: number; } diff --git a/src/legacy/ui/public/courier/index.ts b/src/legacy/ui/public/courier/index.ts index 3c16926d2aba70..c8a06ec2a5518d 100644 --- a/src/legacy/ui/public/courier/index.ts +++ b/src/legacy/ui/public/courier/index.ts @@ -17,8 +17,31 @@ * under the License. */ -export * from './fetch'; -export * from './search_source'; -export * from './search_strategy'; -export * from './utils/courier_inspector_utils'; -export * from './types'; +export { SearchSource } from './search_source'; + +// TODO: Exporting this mock outside of jest tests causes errors because +// jest is undefined. Need to refactor the mock to be consistent with +// other NP-style mocks. +// export { searchSourceMock } from './search_source/mocks'; + +export { + addSearchStrategy, // used externally by Rollups + getSearchErrorType, // used externally by Rollups + hasSearchStategyForIndexPattern, // used externally by Discover + isDefaultTypeIndexPattern, // used externally by Discover + SearchError, // used externally by Visualizations & Rollups +} from './search_strategy'; + +export { + getRequestInspectorStats, + getResponseInspectorStats, +} from './utils/courier_inspector_utils'; + +// types +export { SearchSourceContract } from './search_source'; + +export { + EsQuerySortValue, // used externally by Discover + FetchOptions, // used externally by AggTypes + SortDirection, // used externally by Discover +} from './types'; diff --git a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts index 29921fc7a11d3b..53a857a72c1a3a 100644 --- a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts +++ b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts @@ -18,7 +18,7 @@ */ import { defaultSearchStrategy } from './default_search_strategy'; -import { UiSettingsClientContract } from '../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../core/public'; import { SearchStrategySearchParams } from './types'; const { search } = defaultSearchStrategy; @@ -26,7 +26,7 @@ const { search } = defaultSearchStrategy; function getConfigStub(config: any = {}) { return { get: key => config[key], - } as UiSettingsClientContract; + } as IUiSettingsClient; } const msearchMockResponse: any = Promise.resolve([]); diff --git a/src/legacy/ui/public/courier/utils/courier_inspector_utils.ts b/src/legacy/ui/public/courier/utils/courier_inspector_utils.ts index 2c47fae4cce37a..b4d5d5537333e3 100644 --- a/src/legacy/ui/public/courier/utils/courier_inspector_utils.ts +++ b/src/legacy/ui/public/courier/utils/courier_inspector_utils.ts @@ -28,7 +28,7 @@ import { i18n } from '@kbn/i18n'; import { SearchResponse } from 'elasticsearch'; import { SearchSourceContract, RequestInspectorStats } from '../types'; -function getRequestInspectorStats(searchSource: SearchSourceContract) { +export function getRequestInspectorStats(searchSource: SearchSourceContract) { const stats: RequestInspectorStats = {}; const index = searchSource.getField('index'); @@ -56,7 +56,8 @@ function getRequestInspectorStats(searchSource: SearchSourceContract) { return stats; } -function getResponseInspectorStats( + +export function getResponseInspectorStats( searchSource: SearchSourceContract, resp: SearchResponse ) { @@ -121,5 +122,3 @@ function getResponseInspectorStats( return stats; } - -export { getRequestInspectorStats, getResponseInspectorStats }; diff --git a/src/legacy/ui/public/directives/css_truncate.js b/src/legacy/ui/public/directives/css_truncate.js index 7105563e0ce5f2..ac8e29258fddbf 100644 --- a/src/legacy/ui/public/directives/css_truncate.js +++ b/src/legacy/ui/public/directives/css_truncate.js @@ -20,12 +20,11 @@ import { uiModules } from '../modules'; const module = uiModules.get('kibana'); -module.directive('cssTruncate', function () { +export function CssTruncateProvide() { return { restrict: 'A', scope: {}, link: function ($scope, $elem, attrs) { - $elem.css({ overflow: 'hidden', 'white-space': 'nowrap', @@ -35,10 +34,12 @@ module.directive('cssTruncate', function () { if (attrs.cssTruncateExpandable != null) { $scope.$watch( - function () { return $elem.html(); }, + function () { + return $elem.html(); + }, function () { if ($elem[0].offsetWidth < $elem[0].scrollWidth) { - $elem.css({ 'cursor': 'pointer' }); + $elem.css({ cursor: 'pointer' }); $elem.bind('click', function () { $scope.toggle(); }); @@ -59,6 +60,8 @@ module.directive('cssTruncate', function () { $elem.unbind('click'); $elem.unbind('mouseenter'); }); - } + }, }; -}); +} + +module.directive('cssTruncate', CssTruncateProvide); diff --git a/src/legacy/ui/public/directives/debounce/debounce.js b/src/legacy/ui/public/directives/debounce/debounce.js index 3b384d70887436..06c82d4356a4af 100644 --- a/src/legacy/ui/public/directives/debounce/debounce.js +++ b/src/legacy/ui/public/directives/debounce/debounce.js @@ -24,7 +24,7 @@ import { uiModules } from '../../modules'; const module = uiModules.get('kibana'); -module.service('debounce', ['$timeout', function ($timeout) { +export function DebounceProviderTimeout($timeout) { return function (func, wait, options) { let timeout; let args; @@ -68,7 +68,9 @@ module.service('debounce', ['$timeout', function ($timeout) { return debounce; }; -}]); +} + +module.service('debounce', ['$timeout', DebounceProviderTimeout]); export function DebounceProvider(debounce) { return debounce; diff --git a/src/legacy/ui/public/directives/field_name.js b/src/legacy/ui/public/directives/field_name.js index b159b712381865..aff849fc5602f3 100644 --- a/src/legacy/ui/public/directives/field_name.js +++ b/src/legacy/ui/public/directives/field_name.js @@ -21,7 +21,7 @@ import { uiModules } from '../modules'; import { wrapInI18nContext } from 'ui/i18n'; const module = uiModules.get('kibana'); -module.directive('fieldName', function (config, reactDirective) { +export function FieldNameDirectiveProvider(config, reactDirective) { return reactDirective( wrapInI18nContext(FieldName), [ @@ -34,4 +34,6 @@ module.directive('fieldName', function (config, reactDirective) { useShortDots: config.get('shortDots:enable'), } ); -}); +} + +module.directive('fieldName', FieldNameDirectiveProvider); diff --git a/src/legacy/ui/public/directives/listen/listen.js b/src/legacy/ui/public/directives/listen/listen.js index b877e8ee6b08e0..fddc85a4f49145 100644 --- a/src/legacy/ui/public/directives/listen/listen.js +++ b/src/legacy/ui/public/directives/listen/listen.js @@ -19,22 +19,22 @@ import { uiModules } from '../../modules'; -uiModules.get('kibana') - .run(function ($rootScope) { +export function registerListenEventListener($rootScope) { + /** + * Helper that registers an event listener, and removes that listener when + * the $scope is destroyed. + * + * @param {SimpleEmitter} emitter - the event emitter to listen to + * @param {string} eventName - the event name + * @param {Function} handler - the event handler + * @return {undefined} + */ + $rootScope.constructor.prototype.$listen = function (emitter, eventName, handler) { + emitter.on(eventName, handler); + this.$on('$destroy', function () { + emitter.off(eventName, handler); + }); + }; +} - /** - * Helper that registers an event listener, and removes that listener when - * the $scope is destroyed. - * - * @param {SimpleEmitter} emitter - the event emitter to listen to - * @param {string} eventName - the event name - * @param {Function} handler - the event handler - * @return {undefined} - */ - $rootScope.constructor.prototype.$listen = function (emitter, eventName, handler) { - emitter.on(eventName, handler); - this.$on('$destroy', function () { - emitter.off(eventName, handler); - }); - }; - }); +uiModules.get('kibana').run(registerListenEventListener); diff --git a/src/legacy/ui/public/directives/paginate.js b/src/legacy/ui/public/directives/paginate.js index 7ecd5fefe67103..6663e1fb8b6e48 100644 --- a/src/legacy/ui/public/directives/paginate.js +++ b/src/legacy/ui/public/directives/paginate.js @@ -22,209 +22,215 @@ import { i18n } from '@kbn/i18n'; import { uiModules } from '../modules'; import paginateControlsTemplate from './partials/paginate_controls.html'; -uiModules.get('kibana') - .directive('paginate', function ($parse, $compile) { - return { - restrict: 'E', - scope: true, - link: { - pre: function ($scope, $el, attrs) { - if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; - if ($el.find('paginate-controls.paginate-bottom').length === 0 && attrs.bottomControls) { - $el.append($compile('')($scope)); - } - }, - post: function ($scope, $el, attrs) { - if (_.isUndefined(attrs.topControls)) attrs.topControls = false; - if ($el.find('paginate-controls.paginate-top').length === 0 && attrs.topControls) { - $el.prepend($compile('')($scope)); - } - - const paginate = $scope.paginate; - - // add some getters to the controller powered by attributes - paginate.getList = $parse(attrs.list); - paginate.perPageProp = attrs.perPageProp; - - if (attrs.perPage) { - paginate.perPage = attrs.perPage; - $scope.showSelector = false; - } else { - $scope.showSelector = true; - } - - paginate.otherWidthGetter = $parse(attrs.otherWidth); - - paginate.init(); +export function PaginateDirectiveProvider($parse, $compile) { + return { + restrict: 'E', + scope: true, + link: { + pre: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; + if ($el.find('paginate-controls.paginate-bottom').length === 0 && attrs.bottomControls) { + $el.append($compile('')($scope)); } }, - controllerAs: 'paginate', - controller: function ($scope, $document) { - const self = this; - const ALL = 0; - const allSizeTitle = i18n.translate('common.ui.directives.paginate.size.allDropDownOptionLabel', { - defaultMessage: 'All', - }); + post: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.topControls)) attrs.topControls = false; + if ($el.find('paginate-controls.paginate-top').length === 0 && attrs.topControls) { + $el.prepend($compile('')($scope)); + } - self.sizeOptions = [ - { title: '10', value: 10 }, - { title: '25', value: 25 }, - { title: '100', value: 100 }, - { title: allSizeTitle, value: ALL } - ]; + const paginate = $scope.paginate; - // setup the watchers, called in the post-link function - self.init = function () { + // add some getters to the controller powered by attributes + paginate.getList = $parse(attrs.list); + paginate.perPageProp = attrs.perPageProp; - self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + if (attrs.perPage) { + paginate.perPage = attrs.perPage; + $scope.showSelector = false; + } else { + $scope.showSelector = true; + } - $scope.$watchMulti([ - 'paginate.perPage', - self.perPageProp, - self.otherWidthGetter - ], function (vals, oldVals) { - const intChanges = vals[0] !== oldVals[0]; + paginate.otherWidthGetter = $parse(attrs.otherWidth); - if (intChanges) { - if (!setPerPage(self.perPage)) { + paginate.init(); + }, + }, + controllerAs: 'paginate', + controller: function ($scope, $document) { + const self = this; + const ALL = 0; + const allSizeTitle = i18n.translate( + 'common.ui.directives.paginate.size.allDropDownOptionLabel', + { + defaultMessage: 'All', + } + ); + + self.sizeOptions = [ + { title: '10', value: 10 }, + { title: '25', value: 25 }, + { title: '100', value: 100 }, + { title: allSizeTitle, value: ALL }, + ]; + + // setup the watchers, called in the post-link function + self.init = function () { + self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + + $scope.$watchMulti(['paginate.perPage', self.perPageProp, self.otherWidthGetter], function ( + vals, + oldVals + ) { + const intChanges = vals[0] !== oldVals[0]; + + if (intChanges) { + if (!setPerPage(self.perPage)) { // if we are not able to set the external value, // render now, otherwise wait for the external value // to trigger the watcher again - self.renderList(); - } - return; + self.renderList(); } + return; + } - self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; - if (self.perPage == null) { - self.perPage = ALL; - return; - } + self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + if (self.perPage == null) { + self.perPage = ALL; + return; + } - self.renderList(); - }); + self.renderList(); + }); - $scope.$watch('page', self.changePage); - $scope.$watchCollection(self.getList, function (list) { - $scope.list = list; - self.renderList(); - }); - }; + $scope.$watch('page', self.changePage); + $scope.$watchCollection(self.getList, function (list) { + $scope.list = list; + self.renderList(); + }); + }; - self.goToPage = function (number) { - if (number) { - if (number.hasOwnProperty('number')) number = number.number; - $scope.page = $scope.pages[number - 1] || $scope.pages[0]; - } - }; + self.goToPage = function (number) { + if (number) { + if (number.hasOwnProperty('number')) number = number.number; + $scope.page = $scope.pages[number - 1] || $scope.pages[0]; + } + }; - self.goToTop = function goToTop() { - $document.scrollTop(0); - }; + self.goToTop = function goToTop() { + $document.scrollTop(0); + }; - self.renderList = function () { - $scope.pages = []; - if (!$scope.list) return; + self.renderList = function () { + $scope.pages = []; + if (!$scope.list) return; - const perPage = _.parseInt(self.perPage); - const count = perPage ? Math.ceil($scope.list.length / perPage) : 1; + const perPage = _.parseInt(self.perPage); + const count = perPage ? Math.ceil($scope.list.length / perPage) : 1; - _.times(count, function (i) { - let page; + _.times(count, function (i) { + let page; - if (perPage) { - const start = perPage * i; - page = $scope.list.slice(start, start + perPage); - } else { - page = $scope.list.slice(0); - } + if (perPage) { + const start = perPage * i; + page = $scope.list.slice(start, start + perPage); + } else { + page = $scope.list.slice(0); + } - page.number = i + 1; - page.i = i; + page.number = i + 1; + page.i = i; - page.count = count; - page.first = page.number === 1; - page.last = page.number === count; - page.firstItem = (page.number - 1) * perPage + 1; - page.lastItem = Math.min(page.number * perPage, $scope.list.length); + page.count = count; + page.first = page.number === 1; + page.last = page.number === count; + page.firstItem = (page.number - 1) * perPage + 1; + page.lastItem = Math.min(page.number * perPage, $scope.list.length); - page.prev = $scope.pages[i - 1]; - if (page.prev) page.prev.next = page; + page.prev = $scope.pages[i - 1]; + if (page.prev) page.prev.next = page; - $scope.pages.push(page); - }); + $scope.pages.push(page); + }); - // set the new page, or restore the previous page number - if ($scope.page && $scope.page.i < $scope.pages.length) { - $scope.page = $scope.pages[$scope.page.i]; - } else { - $scope.page = $scope.pages[0]; - } + // set the new page, or restore the previous page number + if ($scope.page && $scope.page.i < $scope.pages.length) { + $scope.page = $scope.pages[$scope.page.i]; + } else { + $scope.page = $scope.pages[0]; + } - if ($scope.page && $scope.onPageChanged) { - $scope.onPageChanged($scope.page); - } - }; + if ($scope.page && $scope.onPageChanged) { + $scope.onPageChanged($scope.page); + } + }; - self.changePage = function (page) { - if (!page) { - $scope.otherPages = null; - return; - } + self.changePage = function (page) { + if (!page) { + $scope.otherPages = null; + return; + } - // setup the list of the other pages to link to - $scope.otherPages = []; - const width = +self.otherWidthGetter($scope) || 5; - let left = page.i - Math.round((width - 1) / 2); - let right = left + width - 1; + // setup the list of the other pages to link to + $scope.otherPages = []; + const width = +self.otherWidthGetter($scope) || 5; + let left = page.i - Math.round((width - 1) / 2); + let right = left + width - 1; - // shift neg count from left to right - if (left < 0) { - right += 0 - left; - left = 0; - } + // shift neg count from left to right + if (left < 0) { + right += 0 - left; + left = 0; + } - // shift extra right nums to left - const lastI = page.count - 1; - if (right > lastI) { - right = lastI; - left = right - width + 1; - } + // shift extra right nums to left + const lastI = page.count - 1; + if (right > lastI) { + right = lastI; + left = right - width + 1; + } - for (let i = left; i <= right; i++) { - const other = $scope.pages[i]; + for (let i = left; i <= right; i++) { + const other = $scope.pages[i]; - if (!other) continue; + if (!other) continue; - $scope.otherPages.push(other); - if (other.last) $scope.otherPages.containsLast = true; - if (other.first) $scope.otherPages.containsFirst = true; - } + $scope.otherPages.push(other); + if (other.last) $scope.otherPages.containsLast = true; + if (other.first) $scope.otherPages.containsFirst = true; + } - if ($scope.onPageChanged) { - $scope.onPageChanged($scope.page); - } - }; + if ($scope.onPageChanged) { + $scope.onPageChanged($scope.page); + } + }; - function setPerPage(val) { - let $ppParent = $scope; + function setPerPage(val) { + let $ppParent = $scope; - while ($ppParent && !_.has($ppParent, self.perPageProp)) { - $ppParent = $ppParent.$parent; - } + while ($ppParent && !_.has($ppParent, self.perPageProp)) { + $ppParent = $ppParent.$parent; + } - if ($ppParent) { - $ppParent[self.perPageProp] = val; - return true; - } + if ($ppParent) { + $ppParent[self.perPageProp] = val; + return true; } } - }; - }) - .directive('paginateControls', function () { + }, + }; +} + +export function PaginateControlsDirectiveProvider() { // this directive is automatically added by paginate if not found within it's $el - return { - restrict: 'E', - template: paginateControlsTemplate - }; - }); + return { + restrict: 'E', + template: paginateControlsTemplate, + }; +} + +uiModules + .get('kibana') + .directive('paginate', PaginateDirectiveProvider) + .directive('paginateControls', PaginateControlsDirectiveProvider); diff --git a/src/legacy/ui/public/directives/watch_multi/watch_multi.js b/src/legacy/ui/public/directives/watch_multi/watch_multi.js index add95e8146f26a..d1b43f74f56aba 100644 --- a/src/legacy/ui/public/directives/watch_multi/watch_multi.js +++ b/src/legacy/ui/public/directives/watch_multi/watch_multi.js @@ -21,10 +21,8 @@ import _ from 'lodash'; import { uiModules } from '../../modules'; import { callEach } from '../../utils/function'; -uiModules.get('kibana') - .config(function ($provide) { - - $provide.decorator('$rootScope', function ($delegate) { +export function watchMultiDecorator($provide) { + $provide.decorator('$rootScope', function ($delegate) { /** * Watch multiple expressions with a single callback. Along * with making code simpler it also merges all of the watcher @@ -53,23 +51,30 @@ uiModules.get('kibana') * @param {Function} fn - the callback function * @return {Function} - an unwatch function, just like the return value of $watch */ - $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { - if (!Array.isArray(expressions)) throw new TypeError('expected an array of expressions to watch'); - if (!_.isFunction(fn)) throw new TypeError('expected a function that is triggered on each watch'); - - const $scope = this; - const vals = new Array(expressions.length); - const prev = new Array(expressions.length); - let fire = false; - let init = 0; - const neededInits = expressions.length; - - // first, register all of the multi-watchers - const unwatchers = expressions.map(function (expr, i) { - expr = normalizeExpression($scope, expr); - if (!expr) return; - - return expr.fn.call($scope, expr.get, function (newVal, oldVal) { + $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { + if (!Array.isArray(expressions)) { + throw new TypeError('expected an array of expressions to watch'); + } + + if (!_.isFunction(fn)) { + throw new TypeError('expected a function that is triggered on each watch'); + } + const $scope = this; + const vals = new Array(expressions.length); + const prev = new Array(expressions.length); + let fire = false; + let init = 0; + const neededInits = expressions.length; + + // first, register all of the multi-watchers + const unwatchers = expressions.map(function (expr, i) { + expr = normalizeExpression($scope, expr); + if (!expr) return; + + return expr.fn.call( + $scope, + expr.get, + function (newVal, oldVal) { if (newVal === oldVal) { init += 1; } @@ -77,60 +82,69 @@ uiModules.get('kibana') vals[i] = newVal; prev[i] = oldVal; fire = true; - }, expr.deep); - }); - - // then, the watcher that checks to see if any of - // the other watchers triggered this cycle - let flip = false; - unwatchers.push($scope.$watch(function () { - if (init < neededInits) return init; - - if (fire) { - fire = false; - flip = !flip; + }, + expr.deep + ); + }); + + // then, the watcher that checks to see if any of + // the other watchers triggered this cycle + let flip = false; + unwatchers.push( + $scope.$watch( + function () { + if (init < neededInits) return init; + + if (fire) { + fire = false; + flip = !flip; + } + return flip; + }, + function () { + if (init < neededInits) return false; + + fn(vals.slice(0), prev.slice(0)); + vals.forEach(function (v, i) { + prev[i] = v; + }); } - return flip; - }, function () { - if (init < neededInits) return false; + ) + ); - fn(vals.slice(0), prev.slice(0)); - vals.forEach(function (v, i) { - prev[i] = v; - }); - })); + return _.partial(callEach, unwatchers); + }; - return _.partial(callEach, unwatchers); + function normalizeExpression($scope, expr) { + if (!expr) return; + const norm = { + fn: $scope.$watch, + deep: false, }; - function normalizeExpression($scope, expr) { - if (!expr) return; - const norm = { - fn: $scope.$watch, - deep: false - }; - - if (_.isFunction(expr)) return _.assign(norm, { get: expr }); - if (_.isObject(expr)) return _.assign(norm, expr); - if (!_.isString(expr)) return; - - if (expr.substr(0, 2) === '[]') { - return _.assign(norm, { - fn: $scope.$watchCollection, - get: expr.substr(2) - }); - } - - if (expr.charAt(0) === '=') { - return _.assign(norm, { - deep: true, - get: expr.substr(1) - }); - } - - return _.assign(norm, { get: expr }); + if (_.isFunction(expr)) return _.assign(norm, { get: expr }); + if (_.isObject(expr)) return _.assign(norm, expr); + if (!_.isString(expr)) return; + + if (expr.substr(0, 2) === '[]') { + return _.assign(norm, { + fn: $scope.$watchCollection, + get: expr.substr(2), + }); } - return $delegate; - }); + if (expr.charAt(0) === '=') { + return _.assign(norm, { + deep: true, + get: expr.substr(1), + }); + } + + return _.assign(norm, { get: expr }); + } + + return $delegate; }); +} + +uiModules.get('kibana').config(watchMultiDecorator); diff --git a/src/legacy/ui/public/es.js b/src/legacy/ui/public/es.js index d1204f8316f0d7..601beba832c33b 100644 --- a/src/legacy/ui/public/es.js +++ b/src/legacy/ui/public/es.js @@ -44,16 +44,17 @@ const plugins = [function (Client, config) { config.connectionClass = CustomAngularConnector; }]; +export function createEsService(esFactory, esUrl, esApiVersion, esRequestTimeout) { + return esFactory({ + host: esUrl, + log: 'info', + requestTimeout: esRequestTimeout, + apiVersion: esApiVersion, + plugins + }); +} + uiModules .get('kibana', ['elasticsearch', 'kibana/config']) - //Elasticsearch client used for requesting data. Connects to the /elasticsearch proxy - .service('es', function (esFactory, esUrl, esApiVersion, esRequestTimeout) { - return esFactory({ - host: esUrl, - log: 'info', - requestTimeout: esRequestTimeout, - apiVersion: esApiVersion, - plugins - }); - }); + .service('es', createEsService); diff --git a/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap b/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap index 2fe29dd29a59ba..6bd9652d2a1b06 100644 --- a/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap +++ b/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap @@ -8,32 +8,28 @@ exports[`is rendered 1`] = ` > In full screen mode, press ESC to exit.

-
+
diff --git a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx index 2e9a39b047e0b2..db4101010f6d67 100644 --- a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx +++ b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx @@ -31,7 +31,7 @@ interface Props { } export class ExitFullScreenButton extends PureComponent { - public componentWillMount() { + public UNSAFE_componentWillMount() { chrome.setVisible(false); } diff --git a/src/legacy/ui/public/fixed_scroll.js b/src/legacy/ui/public/fixed_scroll.js index fcdde7bbdeb328..64c0ae5850bc9a 100644 --- a/src/legacy/ui/public/fixed_scroll.js +++ b/src/legacy/ui/public/fixed_scroll.js @@ -24,30 +24,22 @@ import { DebounceProvider } from 'ui/directives/debounce'; const SCROLLER_HEIGHT = 20; -/** - * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events - * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar - * might be waaaay down the page, like the doc table on Discover. - */ -uiModules - .get('kibana') - .directive('fixedScroll', function (Private) { - const debounce = Private(DebounceProvider); - - return { - restrict: 'A', - link: function ($scope, $el) { - let $window = $(window); - let $scroller = $('
').height(SCROLLER_HEIGHT); +export function FixedScrollProvider(Private) { + const debounce = Private(DebounceProvider); + return { + restrict: 'A', + link: function ($scope, $el) { + let $window = $(window); + let $scroller = $('
').height(SCROLLER_HEIGHT); - /** + /** * Remove the listeners bound in listen() * @type {function} */ - let unlisten = _.noop; + let unlisten = _.noop; - /** + /** * Listen for scroll events on the $scroller and the $el, sets unlisten() * * unlisten must be called before calling or listen() will throw an Error @@ -58,98 +50,109 @@ uiModules * @throws {Error} If unlisten was not called first * @return {undefined} */ - function listen() { - if (unlisten !== _.noop) { - throw new Error('fixedScroll listeners were not cleaned up properly before re-listening!'); - } + function listen() { + if (unlisten !== _.noop) { + throw new Error( + 'fixedScroll listeners were not cleaned up properly before re-listening!' + ); + } - let blockTo; - function bind($from, $to) { - function handler() { - if (blockTo === $to) return (blockTo = null); - $to.scrollLeft((blockTo = $from).scrollLeft()); - } - - $from.on('scroll', handler); - return function () { - $from.off('scroll', handler); - }; + let blockTo; + function bind($from, $to) { + function handler() { + if (blockTo === $to) return (blockTo = null); + $to.scrollLeft((blockTo = $from).scrollLeft()); } - unlisten = _.flow( - bind($el, $scroller), - bind($scroller, $el), - function () { unlisten = _.noop; } - ); + $from.on('scroll', handler); + return function () { + $from.off('scroll', handler); + }; } - /** + unlisten = _.flow( + bind($el, $scroller), + bind($scroller, $el), + function () { + unlisten = _.noop; + } + ); + } + + /** * Revert DOM changes and event listeners * @return {undefined} */ - function cleanUp() { - unlisten(); - $scroller.detach(); - $el.css('padding-bottom', 0); - } + function cleanUp() { + unlisten(); + $scroller.detach(); + $el.css('padding-bottom', 0); + } - /** + /** * Modify the DOM and attach event listeners based on need. * Is called many times to re-setup, must be idempotent * @return {undefined} */ - function setup() { - cleanUp(); + function setup() { + cleanUp(); - const containerWidth = $el.width(); - const contentWidth = $el.prop('scrollWidth'); - const containerHorizOverflow = contentWidth - containerWidth; + const containerWidth = $el.width(); + const contentWidth = $el.prop('scrollWidth'); + const containerHorizOverflow = contentWidth - containerWidth; - const elTop = $el.offset().top - $window.scrollTop(); - const elBottom = elTop + $el.height(); - const windowVertOverflow = elBottom - $window.height(); + const elTop = $el.offset().top - $window.scrollTop(); + const elBottom = elTop + $el.height(); + const windowVertOverflow = elBottom - $window.height(); - const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0; - if (!requireScroller) return; + const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0; + if (!requireScroller) return; - // push the content away from the scroller - $el.css('padding-bottom', SCROLLER_HEIGHT); + // push the content away from the scroller + $el.css('padding-bottom', SCROLLER_HEIGHT); - // fill the scroller with a dummy element that mimics the content - $scroller - .width(containerWidth) - .html($('
').css({ width: contentWidth, height: SCROLLER_HEIGHT })) - .insertAfter($el); + // fill the scroller with a dummy element that mimics the content + $scroller + .width(containerWidth) + .html($('
').css({ width: contentWidth, height: SCROLLER_HEIGHT })) + .insertAfter($el); - // listen for scroll events - listen(); - } + // listen for scroll events + listen(); + } - let width; - let scrollWidth; - function checkWidth() { - const newScrollWidth = $el.prop('scrollWidth'); - const newWidth = $el.width(); + let width; + let scrollWidth; + function checkWidth() { + const newScrollWidth = $el.prop('scrollWidth'); + const newWidth = $el.width(); - if (scrollWidth !== newScrollWidth || width !== newWidth) { - $scope.$apply(setup); + if (scrollWidth !== newScrollWidth || width !== newWidth) { + $scope.$apply(setup); - scrollWidth = newScrollWidth; - width = newWidth; - } + scrollWidth = newScrollWidth; + width = newWidth; } - - const debouncedCheckWidth = debounce(checkWidth, 100, { - invokeApply: false, - }); - $scope.$watch(debouncedCheckWidth); - - // cleanup when the scope is destroyed - $scope.$on('$destroy', function () { - cleanUp(); - debouncedCheckWidth.cancel(); - $scroller = $window = null; - }); } - }; - }); + + const debouncedCheckWidth = debounce(checkWidth, 100, { + invokeApply: false, + }); + $scope.$watch(debouncedCheckWidth); + + // cleanup when the scope is destroyed + $scope.$on('$destroy', function () { + cleanUp(); + debouncedCheckWidth.cancel(); + $scroller = $window = null; + }); + }, + }; +} + +/** + * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events + * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar + * might be waaaay down the page, like the doc table on Discover. + */ +uiModules.get('kibana').directive('fixedScroll', FixedScrollProvider); diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index 145045a90ade8f..0cb1cf897b1665 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -17,30 +17,9 @@ * under the License. */ -import { dataPluginMock } from '../../../../core_plugins/data/public/mocks'; +import { indexPatterns as npIndexPatterns } from '../../../../../plugins/data/public'; -const dataSetup = dataPluginMock.createSetup(); - -// mocks for stateful code -export const { FieldImpl } = dataSetup.indexPatterns!.__LEGACY; -export const { - FieldList, - flattenHitWrapper, - formatHitProvider, - indexPatterns, -} = dataSetup.indexPatterns!; +export const flattenHitWrapper = npIndexPatterns.flattenHitWrapper; // static code -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from '../../../../core_plugins/data/public'; +export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index d0ff0aaa8c72c7..47f6e697c54e9a 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -24,35 +24,17 @@ * from ui/index_patterns for backwards compatibility. */ -import { start as data } from '../../../core_plugins/data/public/legacy'; - -export const { - FieldList, // only used in Discover and StubIndexPattern - flattenHitWrapper, - formatHitProvider, -} = data.indexPatterns; +import { indexPatterns } from '../../../../plugins/data/public'; // static code -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from '../../../core_plugins/data/public'; +export const INDEX_PATTERN_ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS; +export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE; +export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; +export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; +export const validateIndexPattern = indexPatterns.validate; +export const flattenHitWrapper = indexPatterns.flattenHitWrapper; +export const getFromSavedObject = indexPatterns.getFromSavedObject; +export const getRoutes = indexPatterns.getRoutes; // types -export { - Field, - FieldType, - FieldListInterface, - IndexPattern, - IndexPatterns, - StaticIndexPattern, -} from '../../../core_plugins/data/public'; +export { Field, FieldType, IFieldList, IndexPattern } from '../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/indices/validate/validate_index.test.js b/src/legacy/ui/public/indices/validate/validate_index.test.js index 62a6c8610fd40c..f81ba9d4bcab52 100644 --- a/src/legacy/ui/public/indices/validate/validate_index.test.js +++ b/src/legacy/ui/public/indices/validate/validate_index.test.js @@ -18,7 +18,6 @@ */ jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from '../constants'; diff --git a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js index 9073d7588ddcf7..d66965d00b7e2d 100644 --- a/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js +++ b/src/legacy/ui/public/legacy_compat/__tests__/xsrf.js @@ -23,7 +23,8 @@ import sinon from 'sinon'; import ngMock from 'ng_mock'; import { $setupXsrfRequestInterceptor } from '../angular_config'; -import { version } from '../../../../utils/package_json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { version } from '../../../../../core/server/utils/package_json'; const xsrfHeader = 'kbn-version'; const newPlatform = { diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx index 98e95865d7325a..bb6b9f805b4136 100644 --- a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiCallOut } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; -import { DataStart } from '../../../core_plugins/data/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; let bannerId: string; let timeoutId: NodeJS.Timeout | undefined; @@ -40,11 +40,11 @@ let timeoutId: NodeJS.Timeout | undefined; */ export async function ensureDefaultIndexPattern( newPlatform: CoreStart, - data: DataStart, + data: DataPublicPluginStart, $rootScope: IRootScopeService, kbnUrl: any ) { - const patterns = await data.indexPatterns.indexPatterns.getIds(); + const patterns = await data.indexPatterns.getIds(); let defaultId = newPlatform.uiSettings.get('defaultIndex'); let defined = !!defaultId; const exists = contains(patterns, defaultId); diff --git a/src/legacy/ui/public/management/index_pattern_creation/index.js b/src/legacy/ui/public/management/index_pattern_creation/index.js deleted file mode 100644 index 0b677cbfd1f643..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/index.js +++ /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. - */ - -import './register'; -export { IndexPatternCreationFactory } from './index_pattern_creation'; -export { IndexPatternCreationConfig } from './index_pattern_creation_config'; -export { indexPatternTypes, addIndexPatternType } from './index_pattern_types'; diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js b/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js deleted file mode 100644 index 12cecc956ab680..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js +++ /dev/null @@ -1,54 +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 { indexPatternTypes } from './index_pattern_types'; - -class IndexPatternCreation { - constructor(httpClient, type) { - this._allTypes = indexPatternTypes.map(Plugin => new Plugin({ httpClient })); - this._setCurrentType(type); - } - - _setCurrentType = (type) => { - const index = type ? indexPatternTypes.findIndex(Plugin => Plugin.key === type) : -1; - this._currentType = index > -1 && this._allTypes[index] ? this._allTypes[index] : null; - } - - getType = () => { - return this._currentType || null; - } - - getIndexPatternCreationOptions = async (urlHandler) => { - const options = []; - await Promise.all(this._allTypes.map(async type => { - const option = type.getIndexPatternCreationOption ? await type.getIndexPatternCreationOption(urlHandler) : null; - if(option) { - options.push(option); - } - })); - return options; - } -} - -export const IndexPatternCreationFactory = (Private, $http) => { - return (type = 'default') => { - const indexPatternCreationProvider = new IndexPatternCreation($http, type); - return indexPatternCreationProvider; - }; -}; diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js b/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js deleted file mode 100644 index 9cf1e6c4ed3735..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js +++ /dev/null @@ -1,94 +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'; - -const indexPatternTypeName = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'index pattern' }); - -const indexPatternButtonText = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Standard index pattern' }); - -const indexPatternButtonDescription = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform full aggregations against any data' }); - -export class IndexPatternCreationConfig { - static key = 'default'; - - constructor({ - type = undefined, - name = indexPatternTypeName, - showSystemIndices = true, - httpClient = null, - isBeta = false, - }) { - this.type = type; - this.name = name; - this.showSystemIndices = showSystemIndices; - this.httpClient = httpClient; - this.isBeta = isBeta; - } - - async getIndexPatternCreationOption(urlHandler) { - return { - text: indexPatternButtonText, - description: indexPatternButtonDescription, - testSubj: `createStandardIndexPatternButton`, - onClick: () => { - urlHandler('/management/kibana/index_pattern'); - }, - }; - } - - getIndexPatternType = () => { - return this.type; - } - - getIndexPatternName = () => { - return this.name; - } - - getIsBeta = () => { - return this.isBeta; - } - - getShowSystemIndices = () => { - return this.showSystemIndices; - } - - getIndexTags() { - return []; - } - - checkIndicesForErrors = () => { - return undefined; - } - - getIndexPatternMappings = () => { - return {}; - } - - renderPrompt = () => { - return null; - } - - getFetchForWildcardOptions = () => { - return {}; - } -} diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_types.js b/src/legacy/ui/public/management/index_pattern_creation/index_pattern_types.js deleted file mode 100644 index 9e94a52278ab24..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_types.js +++ /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 const indexPatternTypes = []; -export const addIndexPatternType = (type) => indexPatternTypes.push(type); diff --git a/src/legacy/ui/public/management/index_pattern_creation/register.js b/src/legacy/ui/public/management/index_pattern_creation/register.js deleted file mode 100644 index bca4387b496fd8..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/register.js +++ /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. - */ - -import { IndexPatternCreationConfig } from './index_pattern_creation_config'; -import { addIndexPatternType } from './index_pattern_types'; - -addIndexPatternType(IndexPatternCreationConfig); diff --git a/src/legacy/ui/public/management/index_pattern_list/index.js b/src/legacy/ui/public/management/index_pattern_list/index.js deleted file mode 100644 index a77a3f1f61f2ce..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/index.js +++ /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. - */ - -import './register'; -export { IndexPatternListFactory } from './index_pattern_list'; -export { IndexPatternListConfig } from './index_pattern_list_config'; -export { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry'; diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js b/src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js deleted file mode 100644 index 9ff82db27fefce..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js +++ /dev/null @@ -1,52 +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 { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry'; - -class IndexPatternList { - constructor(registry) { - this._plugins = registry.inOrder.map(Plugin => new Plugin()); - } - - getIndexPatternTags = (indexPattern, isDefault) => { - return this._plugins.reduce((tags, plugin) => { - return plugin.getIndexPatternTags ? tags.concat(plugin.getIndexPatternTags(indexPattern, isDefault)) : tags; - }, []); - } - - getFieldInfo = (indexPattern, field) => { - return this._plugins.reduce((info, plugin) => { - return plugin.getFieldInfo ? info.concat(plugin.getFieldInfo(indexPattern, field)) : info; - }, []); - } - - areScriptedFieldsEnabled = (indexPattern) => { - return this._plugins.every((plugin) => { - return plugin.areScriptedFieldsEnabled ? plugin.areScriptedFieldsEnabled(indexPattern) : true; - }); - } -} - -export const IndexPatternListFactory = (Private) => { - return function () { - const indexPatternListRegistry = Private(IndexPatternListConfigRegistry); - const indexPatternListProvider = new IndexPatternList(indexPatternListRegistry); - return indexPatternListProvider; - }; -}; diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config.js b/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config.js deleted file mode 100644 index cf1389fbab9f5e..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config.js +++ /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. - */ - -export class IndexPatternListConfig { - static key = 'default'; - - getIndexPatternTags = (indexPattern, isDefault) => { - return isDefault ? [{ - key: 'default', - name: 'Default', - }] : []; - } - - getFieldInfo = () => { - return []; - } - - areScriptedFieldsEnabled = () => { - return true; - } -} diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js b/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js deleted file mode 100644 index 9b1bfca284ee67..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js +++ /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. - */ - -import { uiRegistry } from 'ui/registry/_registry'; - -export const IndexPatternListConfigRegistry = uiRegistry({ - name: 'indexPatternList', - index: ['name'], - order: ['order'], -}); diff --git a/src/legacy/ui/public/management/index_pattern_list/register.js b/src/legacy/ui/public/management/index_pattern_list/register.js deleted file mode 100644 index 6488ddd399b7b9..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/register.js +++ /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. - */ - -import { IndexPatternListConfig } from './index_pattern_list_config'; -import { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry'; - -IndexPatternListConfigRegistry.register(() => IndexPatternListConfig); diff --git a/src/legacy/ui/public/management/saved_objects_management/index.ts b/src/legacy/ui/public/management/saved_objects_management/index.ts deleted file mode 100644 index c7223a859ee37d..00000000000000 --- a/src/legacy/ui/public/management/saved_objects_management/index.ts +++ /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. - */ - -export { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; -export { - SavedObjectsManagementAction, - SavedObjectsManagementRecord, - SavedObjectsManagementRecordReference, -} from './saved_objects_management_action'; -export { - processImportResponse, - ProcessedImportResponse, -} from '../../../../core_plugins/kibana/public/management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/ui/public/management/section.js b/src/legacy/ui/public/management/section.js index d5e70c03d42595..b6952695cd910d 100644 --- a/src/legacy/ui/public/management/section.js +++ b/src/legacy/ui/public/management/section.js @@ -42,7 +42,7 @@ export class ManagementSection { this.id = id; this.items = new IndexedArray({ index: ['id'], - order: ['order'] + order: ['order'], }); this.visible = true; this.disabled = false; @@ -51,13 +51,14 @@ export class ManagementSection { this.url = ''; assign(this, options); - } get visibleItems() { return this.items.inOrder.filter(item => { const capabilityManagementSection = capabilities.get().management[this.id]; - const itemCapability = capabilityManagementSection ? capabilityManagementSection[item.id] : null; + const itemCapability = capabilityManagementSection + ? capabilityManagementSection[item.id] + : null; return item.visible && itemCapability !== false; }); @@ -95,10 +96,10 @@ export class ManagementSection { } /** - * Deregisters a section - * - * @param {string} id - */ + * Deregisters a section + * + * @param {string} id + */ deregister(id) { this.items.remove(item => item.id === id); listeners.forEach(fn => fn(this.items)); diff --git a/src/legacy/ui/public/management/section.test.js b/src/legacy/ui/public/management/section.test.js index a45fca426e2b42..e6363f2e164b48 100644 --- a/src/legacy/ui/public/management/section.test.js +++ b/src/legacy/ui/public/management/section.test.js @@ -24,10 +24,10 @@ jest.mock('ui/capabilities', () => ({ kibana: { sampleFeature1: true, sampleFeature2: false, - } - } - }) - } + }, + }, + }), + }, })); import { ManagementSection } from './section'; @@ -115,7 +115,9 @@ describe('ManagementSection', () => { it('calls listener when item added', () => { let listerCalled = false; - const listenerFn = () => { listerCalled = true; }; + const listenerFn = () => { + listerCalled = true; + }; section.addListener(listenerFn); section.register('about'); @@ -131,12 +133,12 @@ describe('ManagementSection', () => { section.register('about'); }); - it ('deregisters an existing section', () => { + it('deregisters an existing section', () => { section.deregister('about'); expect(section.items).toHaveLength(0); }); - it ('allows deregistering a section more than once', () => { + it('allows deregistering a section more than once', () => { section.deregister('about'); section.deregister('about'); expect(section.items).toHaveLength(0); @@ -144,7 +146,9 @@ describe('ManagementSection', () => { it('calls listener when item added', () => { let listerCalled = false; - const listenerFn = () => { listerCalled = true; }; + const listenerFn = () => { + listerCalled = true; + }; section.addListener(listenerFn); section.deregister('about'); @@ -202,7 +206,9 @@ describe('ManagementSection', () => { }); it('can be ordered', () => { - const ids = section.items.inOrder.map((i) => { return i.id; }); + const ids = section.items.inOrder.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'two', 'three']); }); }); @@ -256,20 +262,26 @@ describe('ManagementSection', () => { }); it('maintains the order', () => { - const ids = section.visibleItems.map((i) => { return i.id; }); + const ids = section.visibleItems.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'two', 'three']); }); it('does not include hidden items', () => { section.getSection('two').hide(); - const ids = section.visibleItems.map((i) => { return i.id; }); + const ids = section.visibleItems.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'three']); }); it('does not include visible items hidden via uiCapabilities', () => { section.register('sampleFeature2', { order: 4, visible: true }); - const ids = section.visibleItems.map((i) => { return i.id; }); + const ids = section.visibleItems.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'two', 'three']); }); }); diff --git a/src/legacy/ui/public/management/sections_register.js b/src/legacy/ui/public/management/sections_register.js index 20ed6242e96951..cf47febdad4ebc 100644 --- a/src/legacy/ui/public/management/sections_register.js +++ b/src/legacy/ui/public/management/sections_register.js @@ -22,15 +22,15 @@ import { i18n } from '@kbn/i18n'; export const management = new ManagementSection('management', { display: i18n.translate('common.ui.management.displayName', { - defaultMessage: 'Management' - }) + defaultMessage: 'Management', + }), }); management.register('data', { display: i18n.translate('common.ui.management.connectDataDisplayName', { - defaultMessage: 'Connect Data' + defaultMessage: 'Connect Data', }), - order: 0 + order: 0, }); management.register('elasticsearch', { 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 ff89ef69d53cad..f8850d1691cddf 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 @@ -80,6 +80,14 @@ export const npSetup = { timefilter: sinon.fake(), history: sinon.fake(), }, + savedQueries: { + saveQuery: sinon.fake(), + getAllSavedQueries: sinon.fake(), + findSavedQueries: sinon.fake(), + getSavedQuery: sinon.fake(), + deleteSavedQuery: sinon.fake(), + getSavedQueryCount: sinon.fake(), + } }, fieldFormats: getFieldFormatsRegistry(mockUiSettings), }, @@ -106,8 +114,10 @@ export const npSetup = { registerAction: sinon.fake(), registerTrigger: sinon.fake(), }, - feature_catalogue: { - register: sinon.fake(), + home: { + featureCatalogue: { + register: sinon.fake(), + }, }, }, }; @@ -191,6 +201,7 @@ export const npStart = { }, getTime: sinon.fake(), setTime: sinon.fake(), + getActiveBounds: sinon.fake(), getBounds: sinon.fake(), calculateBounds: sinon.fake(), createFilter: sinon.fake(), @@ -220,8 +231,10 @@ export const npStart = { getTriggerActions: sinon.fake(), getTriggerCompatibleActions: sinon.fake(), }, - feature_catalogue: { - register: sinon.fake(), + home: { + featureCatalogue: { + register: sinon.fake(), + }, }, }, }; diff --git a/src/legacy/ui/public/registry/doc_views.ts b/src/legacy/ui/public/registry/doc_views.ts index 097808c5dcfcc5..bf1e8416ae66d8 100644 --- a/src/legacy/ui/public/registry/doc_views.ts +++ b/src/legacy/ui/public/registry/doc_views.ts @@ -21,6 +21,12 @@ import { DocView, DocViewInput, ElasticSearchHit, DocViewInputFn } from './doc_v export { DocViewRenderProps, DocView, DocViewRenderFn } from './doc_views_types'; +export interface DocViewsRegistry { + docViews: DocView[]; + addDocView: (docView: DocViewInput) => void; + getDocViewsSorted: (hit: ElasticSearchHit) => DocView[]; +} + export const docViews: DocView[] = []; /** diff --git a/src/legacy/ui/public/registry/doc_views_helpers.tsx b/src/legacy/ui/public/registry/doc_views_helpers.tsx index 1ff00713b10efe..d9e42e71dfff1d 100644 --- a/src/legacy/ui/public/registry/doc_views_helpers.tsx +++ b/src/legacy/ui/public/registry/doc_views_helpers.tsx @@ -26,7 +26,7 @@ import { AngularController, AngularDirective, } from './doc_views_types'; -import { DocViewerError } from '../../../core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error'; +import { DocViewerError } from '../../../core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error'; /** * Compiles and injects the give angular template into the given dom node diff --git a/src/legacy/ui/public/render_complete/directive.js b/src/legacy/ui/public/render_complete/directive.js index 6bde2293898b64..0e37ec964d3f03 100644 --- a/src/legacy/ui/public/render_complete/directive.js +++ b/src/legacy/ui/public/render_complete/directive.js @@ -20,13 +20,14 @@ import { uiModules } from '../modules'; import { RenderCompleteHelper } from '../../../../plugins/kibana_utils/public'; -uiModules - .get('kibana') - .directive('renderComplete', () => ({ +export function createRenderCompleteDirective() { + return { controller($scope, $element) { const el = $element[0]; const renderCompleteHelper = new RenderCompleteHelper(el); - $scope.$on('$destroy', renderCompleteHelper.destroy); } - })); + }; +} + +uiModules.get('kibana').directive('renderComplete', createRenderCompleteDirective); diff --git a/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx b/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx deleted file mode 100644 index 5b787eb2655093..00000000000000 --- a/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { npStart } from 'ui/new_platform'; -import { IconType } from '@elastic/eui'; -import { SavedObjectAttributes } from 'src/core/server'; -import { SimpleSavedObject } from 'src/core/public'; -import { SavedObjectFinder as SavedObjectFinderNP } from '../../../../../plugins/kibana_react/public'; - -/** - * DO NOT USE THIS COMPONENT, IT IS DEPRECATED. - * Use the one in `src/plugins/kibana_react` instead. - */ - -export interface SavedObjectMetaData { - type: string; - name: string; - getIconForSavedObject(savedObject: SimpleSavedObject): IconType; - getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; - showSavedObject?(savedObject: SimpleSavedObject): boolean; -} - -interface BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - onChoose?: ( - id: SimpleSavedObject['id'], - type: SimpleSavedObject['type'], - name: string - ) => void; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - noItemsMessage?: React.ReactNode; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - savedObjectMetaData: Array>; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - showFilter?: boolean; -} - -interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - initialPageSize?: undefined; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - fixedPageSize: number; -} - -interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - initialPageSize?: 5 | 10 | 15 | 25; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - fixedPageSize?: undefined; -} -type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; - -export const SavedObjectFinder: React.FC = props => ( - -); diff --git a/src/legacy/ui/public/state_management/__tests__/state.js b/src/legacy/ui/public/state_management/__tests__/state.js index cbeb5e3650a163..475d7c44a5f5a6 100644 --- a/src/legacy/ui/public/state_management/__tests__/state.js +++ b/src/legacy/ui/public/state_management/__tests__/state.js @@ -26,13 +26,11 @@ import { toastNotifications } from '../../notify'; import * as FatalErrorNS from '../../notify/fatal_error'; import { StateProvider } from '../state'; import { - unhashQueryString, -} from '../state_hashing'; -import { + unhashQuery, createStateHash, isStateHash, -} from '../state_storage'; -import { HashedItemStore } from '../state_storage/hashed_item_store'; + HashedItemStore +} from '../../../../../plugins/kibana_utils/public'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; import { EventsProvider } from '../../events'; @@ -60,9 +58,7 @@ describe('State Management', () => { const hashedItemStore = new HashedItemStore(store); const state = new State(param, initial, hashedItemStore); - const getUnhashedSearch = state => { - return unhashQueryString($location.search(), [ state ]); - }; + const getUnhashedSearch = () => unhashQuery($location.search()); return { store, hashedItemStore, state, getUnhashedSearch }; }; diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index 8d55a6929a617a..e868abb98c852e 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -35,12 +35,7 @@ import { fatalError, toastNotifications } from '../notify'; import './config_provider'; import { createLegacyClass } from '../utils/legacy_class'; import { callEach } from '../utils/function'; - -import { - createStateHash, - HashedItemStoreSingleton, - isStateHash, -} from './state_storage'; +import { hashedItemStore, isStateHash, createStateHash } from '../../../../plugins/kibana_utils/public'; export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); @@ -54,13 +49,13 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon function State( urlParam, defaults, - hashedItemStore = HashedItemStoreSingleton + _hashedItemStore = hashedItemStore ) { State.Super.call(this); this.setDefaults(defaults); this._urlParam = urlParam || '_s'; - this._hashedItemStore = hashedItemStore; + this._hashedItemStore = _hashedItemStore; // When the URL updates we need to fetch the values from the URL this._cleanUpListeners = _.partial(callEach, [ @@ -293,9 +288,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon // We need to strip out Angular-specific properties. const json = angular.toJson(state); - const hash = createStateHash(json, hash => { - return this._hashedItemStore.getItem(hash); - }); + const hash = createStateHash(json); const isItemSet = this._hashedItemStore.setItem(hash, json); if (isItemSet) { @@ -323,6 +316,27 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return this._urlParam; }; + /** + * Returns an object with each property name and value corresponding to the entries in this collection + * excluding fields started from '$', '_' and all methods + * + * @return {object} + */ + State.prototype.toObject = function () { + return _.omit(this, (value, key) => { + return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); + }); + }; + + /** Alias for method 'toObject' + * + * @obsolete Please use 'toObject' method instead + * @return {object} + */ + State.prototype.toJSON = function () { + return this.toObject(); + }; + return State; } diff --git a/src/legacy/ui/public/state_management/state_hashing/__tests__/hash_url.js b/src/legacy/ui/public/state_management/state_hashing/__tests__/hash_url.js deleted file mode 100644 index 1d5b88a22d9c61..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/__tests__/hash_url.js +++ /dev/null @@ -1,158 +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 sinon from 'sinon'; -import { parse as parseUrl } from 'url'; - -import { StateProvider } from '../../state'; -import { hashUrl } from '..'; - -describe('hashUrl', function () { - let State; - - beforeEach(ngMock.module('kibana')); - - beforeEach(ngMock.inject((Private, config) => { - State = Private(StateProvider); - sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(true); - })); - - describe('throws error', () => { - it('if states parameter is null', () => { - expect(() => { - hashUrl(null, ''); - }).to.throwError(); - }); - - it('if states parameter is empty array', () => { - expect(() => { - hashUrl([], ''); - }).to.throwError(); - }); - }); - - describe('does nothing', () => { - let states; - beforeEach(() => { - states = [new State('testParam')]; - }); - it('if url is empty', () => { - const url = ''; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if just a host and port', () => { - const url = 'https://localhost:5601'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if just a path', () => { - const url = 'https://localhost:5601/app/kibana'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if just a path and query', () => { - const url = 'https://localhost:5601/app/kibana?foo=bar'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if empty hash with query', () => { - const url = 'https://localhost:5601/app/kibana?foo=bar#'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if query parameter matches and there is no hash', () => { - const url = 'https://localhost:5601/app/kibana?testParam=(yes:!t)'; - expect(hashUrl(states, url)).to.be(url); - }); - - it(`if query parameter matches and it's before the hash`, () => { - const url = 'https://localhost:5601/app/kibana?testParam=(yes:!t)'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if empty hash without query', () => { - const url = 'https://localhost:5601/app/kibana#'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if hash is just a path', () => { - const url = 'https://localhost:5601/app/kibana#/discover'; - expect(hashUrl(states, url)).to.be(url); - }); - - it('if hash does not have matching query string vals', () => { - const url = 'https://localhost:5601/app/kibana#/discover?foo=bar'; - expect(hashUrl(states, url)).to.be(url); - }); - }); - - describe('replaces querystring value with hash', () => { - const getAppQuery = (url) => { - const parsedUrl = parseUrl(url); - const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); - - return parsedAppUrl.query; - }; - - it('if using a single State', () => { - const stateParamKey = 'testParam'; - const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey}=(yes:!t)`; - const mockHashedItemStore = { - getItem: () => null, - setItem: sinon.stub().returns(true) - }; - const state = new State(stateParamKey, {}, mockHashedItemStore); - - const actualUrl = hashUrl([state], url); - - expect(mockHashedItemStore.setItem.calledOnce).to.be(true); - - const appQuery = getAppQuery(actualUrl); - - const hashKey = mockHashedItemStore.setItem.firstCall.args[0]; - expect(appQuery[stateParamKey]).to.eql(hashKey); - }); - - it('if using multiple States', () => { - const stateParamKey1 = 'testParam1'; - const stateParamKey2 = 'testParam2'; - const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=(yes:!t)&${stateParamKey2}=(yes:!f)`; - const mockHashedItemStore = { - getItem: () => null, - setItem: sinon.stub().returns(true) - }; - const state1 = new State(stateParamKey1, {}, mockHashedItemStore); - const state2 = new State(stateParamKey2, {}, mockHashedItemStore); - - const actualUrl = hashUrl([state1, state2], url); - - expect(mockHashedItemStore.setItem.calledTwice).to.be(true); - - const appQuery = getAppQuery(actualUrl); - - const hashKey1 = mockHashedItemStore.setItem.firstCall.args[0]; - const hashKey2 = mockHashedItemStore.setItem.secondCall.args[0]; - expect(appQuery[stateParamKey1]).to.eql(hashKey1); - expect(appQuery[stateParamKey2]).to.eql(hashKey2); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/state_hashing/__tests__/unhash_url.js b/src/legacy/ui/public/state_management/state_hashing/__tests__/unhash_url.js deleted file mode 100644 index 671194ecb50f55..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/__tests__/unhash_url.js +++ /dev/null @@ -1,87 +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 sinon from 'sinon'; - -import { StateProvider } from '../../state'; -import { unhashUrl } from '..'; - -describe('unhashUrl', () => { - let unhashableStates; - - beforeEach(ngMock.module('kibana')); - - beforeEach(ngMock.inject(Private => { - const State = Private(StateProvider); - const unhashableState = new State('testParam'); - sinon.stub(unhashableState, 'translateHashToRison').withArgs('hash').returns('replacement'); - unhashableStates = [unhashableState]; - })); - - describe('does nothing', () => { - it('if missing input', () => { - expect(() => { - unhashUrl(); - }).to.not.throwError(); - }); - - it('if just a host and port', () => { - const url = 'https://localhost:5601'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - - it('if just a path', () => { - const url = 'https://localhost:5601/app/kibana'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - - it('if just a path and query', () => { - const url = 'https://localhost:5601/app/kibana?foo=bar'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - - it('if empty hash with query', () => { - const url = 'https://localhost:5601/app/kibana?foo=bar#'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - - it('if empty hash without query', () => { - const url = 'https://localhost:5601/app/kibana#'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - - it('if hash is just a path', () => { - const url = 'https://localhost:5601/app/kibana#/discover'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - - it('if hash does not have matching query string vals', () => { - const url = 'https://localhost:5601/app/kibana#/discover?foo=bar'; - expect(unhashUrl(url, unhashableStates)).to.be(url); - }); - }); - - it('replaces query string vals in hash for matching states with output of state.toRISON()', () => { - const urlWithHashes = 'https://localhost:5601/#/?foo=bar&testParam=hash'; - const exp = 'https://localhost:5601/#/?foo=bar&testParam=replacement'; - expect(unhashUrl(urlWithHashes, unhashableStates)).to.be(exp); - }); -}); diff --git a/src/legacy/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts b/src/legacy/ui/public/state_management/state_hashing/get_unhashable_states_provider.ts deleted file mode 100644 index 6c43947640ed39..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/get_unhashable_states_provider.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. - */ - -import { AppState } from '../app_state'; -import { GlobalState } from '../global_state'; -import { State } from '../state'; - -export function getUnhashableStatesProvider(getAppState: () => AppState, globalState: GlobalState) { - return function getUnhashableStates(): State[] { - return [getAppState(), globalState].filter(Boolean); - }; -} diff --git a/src/legacy/ui/public/state_management/state_hashing/hash_url.js b/src/legacy/ui/public/state_management/state_hashing/hash_url.js deleted file mode 100644 index 973266fcf86852..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/hash_url.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 encodeUriQuery from 'encode-uri-query'; -import rison from 'rison-node'; -import { parse as parseUrl, format as formatUrl } from 'url'; -import { stringify as stringifyQuerystring } from 'querystring'; - -const conservativeStringifyQuerystring = (query) => { - return stringifyQuerystring(query, null, null, { - encodeURIComponent: encodeUriQuery - }); -}; - -const hashStateInQuery = (state, query) => { - const name = state.getQueryParamName(); - const value = query[name]; - if (!value) { - return { name, value }; - } - - const decodedValue = rison.decode(value); - const hashedValue = state.toQueryParam(decodedValue); - return { name, value: hashedValue }; -}; - -const hashStatesInQuery = (states, query) => { - const hashedQuery = states.reduce((result, state) => { - const { name, value } = hashStateInQuery(state, query); - if (value) { - result[name] = value; - } - return result; - }, {}); - - - return { - ...query, - ...hashedQuery - }; -}; - -export const hashUrl = (states, redirectUrl) => { - // we need states to proceed, throwing an error if we don't have any - if (states === null || !states.length) { - throw new Error('states parameter must be an Array with length greater than 0'); - } - - const parsedUrl = parseUrl(redirectUrl); - // if we don't have a hash, we return the redirectUrl without hashing anything - if (!parsedUrl.hash) { - return redirectUrl; - } - - // The URLs that we use aren't "conventional" and the hash is sometimes appearing before - // the querystring, even though conventionally they appear after it. The parsedUrl - // is the entire URL, and the parsedAppUrl is everything after the hash. - // - // EXAMPLE - // parsedUrl: /app/kibana#/visualize/edit/somelongguid?g=()&a=() - // parsedAppUrl: /visualize/edit/somelongguid?g=()&a=() - const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); - - // the parsedAppUrl actually has the query that we care about - const query = parsedAppUrl.query; - - const newQuery = hashStatesInQuery(states, query); - - const newHash = formatUrl({ - search: conservativeStringifyQuerystring(newQuery), - pathname: parsedAppUrl.pathname - }); - - return formatUrl({ - hash: `#${newHash}`, - host: parsedUrl.host, - search: parsedUrl.search, - pathname: parsedUrl.pathname, - protocol: parsedUrl.protocol, - }); -}; diff --git a/src/legacy/ui/public/state_management/state_hashing/index.d.ts b/src/legacy/ui/public/state_management/state_hashing/index.d.ts deleted file mode 100644 index 163d9ed07f2cc3..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/index.d.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 function unhashUrl(url: string, kbnStates: any[]): any; diff --git a/src/legacy/ui/public/state_management/state_hashing/index.js b/src/legacy/ui/public/state_management/state_hashing/index.js deleted file mode 100644 index fe868a9b67bde3..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/index.js +++ /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 { getUnhashableStatesProvider } from './get_unhashable_states_provider'; -export { hashUrl } from './hash_url'; -export { unhashQueryString } from './unhash_query_string'; -export { unhashUrl } from './unhash_url'; diff --git a/src/legacy/ui/public/state_management/state_hashing/unhash_query_string.ts b/src/legacy/ui/public/state_management/state_hashing/unhash_query_string.ts deleted file mode 100644 index 242b840282f39e..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/unhash_query_string.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 { mapValues } from 'lodash'; -import { ParsedUrlQuery } from 'querystring'; -import { State } from '../state'; - -/** - * Takes in a parsed url query and state objects, finding the state objects that match the query parameters and expanding - * the hashed state. For example, a url query string like '?_a=@12353&_g=@19028df' will become - * '?_a=[expanded app state here]&_g=[expanded global state here]. This is used when storeStateInSessionStorage is turned on. - */ -export function unhashQueryString( - parsedQueryString: ParsedUrlQuery, - states: State[] -): ParsedUrlQuery { - return mapValues(parsedQueryString, (val, key) => { - const state = states.find(s => key === s.getQueryParamName()); - return state ? state.translateHashToRison(val) : val; - }); -} diff --git a/src/legacy/ui/public/state_management/state_hashing/unhash_url.js b/src/legacy/ui/public/state_management/state_hashing/unhash_url.js deleted file mode 100644 index 272237a8b81eff..00000000000000 --- a/src/legacy/ui/public/state_management/state_hashing/unhash_url.js +++ /dev/null @@ -1,68 +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 { - parse as parseUrl, - format as formatUrl, -} from 'url'; - -import encodeUriQuery from 'encode-uri-query'; - -import { - stringify as stringifyQueryString -} from 'querystring'; - -import { unhashQueryString } from './unhash_query_string'; - -export function unhashUrl(urlWithHashes, states) { - if (!urlWithHashes) return urlWithHashes; - - const urlWithHashesParsed = parseUrl(urlWithHashes, true); - if (!urlWithHashesParsed.hostname) { - // passing a url like "localhost:5601" or "/app/kibana" should be prevented - throw new TypeError( - 'Only absolute urls should be passed to `unhashUrl()`. ' + - 'Unable to detect url hostname.' - ); - } - - if (!urlWithHashesParsed.hash) return urlWithHashes; - - const appUrl = urlWithHashesParsed.hash.slice(1); // trim the # - if (!appUrl) return urlWithHashes; - - const appUrlParsed = parseUrl(urlWithHashesParsed.hash.slice(1), true); - if (!appUrlParsed.query) return urlWithHashes; - - const appQueryWithoutHashes = unhashQueryString(appUrlParsed.query || {}, states); - - // encodeUriQuery implements the less-aggressive encoding done naturally by - // the browser. We use it to generate the same urls the browser would - const appQueryStringWithoutHashes = stringifyQueryString(appQueryWithoutHashes, null, null, { - encodeURIComponent: encodeUriQuery - }); - - return formatUrl({ - ...urlWithHashesParsed, - hash: formatUrl({ - pathname: appUrlParsed.pathname, - search: appQueryStringWithoutHashes, - }) - }); -} diff --git a/src/legacy/ui/public/state_management/state_storage/__tests__/hashed_item_store.js b/src/legacy/ui/public/state_management/state_storage/__tests__/hashed_item_store.js deleted file mode 100644 index dfb726ce53299d..00000000000000 --- a/src/legacy/ui/public/state_management/state_storage/__tests__/hashed_item_store.js +++ /dev/null @@ -1,352 +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 { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -import { HashedItemStore } from '../hashed_item_store'; - -describe('hashedItemStore', () => { - describe('interface', () => { - describe('#constructor', () => { - it('retrieves persisted index from sessionStorage', () => { - const sessionStorage = new StubBrowserStorage(); - sinon.spy(sessionStorage, 'getItem'); - - new HashedItemStore(sessionStorage); - sinon.assert.calledWith(sessionStorage.getItem, HashedItemStore.PERSISTED_INDEX_KEY); - sessionStorage.getItem.restore(); - }); - - it('sorts indexed items by touched property', () => { - const a = { - hash: 'a', - touched: 0, - }; - const b = { - hash: 'b', - touched: 2, - }; - const c = { - hash: 'c', - touched: 1, - }; - const sessionStorage = new StubBrowserStorage(); - if (!HashedItemStore.PERSISTED_INDEX_KEY) { - // This is very brittle and depends upon HashedItemStore implementation details, - // so let's protect ourselves from accidentally breaking this test. - throw new Error('Missing HashedItemStore.PERSISTED_INDEX_KEY'); - } - sessionStorage.setItem(HashedItemStore.PERSISTED_INDEX_KEY, JSON.stringify({ a, b, c })); - - const hashedItemStore = new HashedItemStore(sessionStorage); - expect(hashedItemStore._indexedItems).to.eql([a, c, b]); - }); - }); - - describe('#setItem', () => { - describe('if the item exists in sessionStorage', () => { - let sessionStorage; - let hashedItemStore; - const hash = 'a'; - const item = JSON.stringify({}); - - beforeEach(() => { - sessionStorage = new StubBrowserStorage(); - hashedItemStore = new HashedItemStore(sessionStorage); - }); - - it('persists the item in sessionStorage', () => { - hashedItemStore.setItem(hash, item); - expect(sessionStorage.getItem(hash)).to.equal(item); - }); - - it('returns true', () => { - const result = hashedItemStore.setItem(hash, item); - expect(result).to.equal(true); - }); - }); - - describe(`if the item doesn't exist in sessionStorage`, () => { - describe(`if there's storage space`, () => { - let sessionStorage; - let hashedItemStore; - const hash = 'a'; - const item = JSON.stringify({}); - - beforeEach(() => { - sessionStorage = new StubBrowserStorage(); - hashedItemStore = new HashedItemStore(sessionStorage); - }); - - it('persists the item in sessionStorage', () => { - hashedItemStore.setItem(hash, item); - expect(sessionStorage.getItem(hash)).to.equal(item); - }); - - it('returns true', () => { - const result = hashedItemStore.setItem(hash, item); - expect(result).to.equal(true); - }); - }); - - describe(`if there isn't storage space`, () => { - let fakeTimer; - let sessionStorage; - let hashedItemStore; - let storageSizeLimit; - const hash = 'a'; - const item = JSON.stringify({}); - - function setItemLater(hash, item) { - // Move time forward, so this item will be "touched" most recently. - fakeTimer.tick(1); - return hashedItemStore.setItem(hash, item); - } - - beforeEach(() => { - // Control time. - fakeTimer = sinon.useFakeTimers(Date.now()); - - sessionStorage = new StubBrowserStorage(); - hashedItemStore = new HashedItemStore(sessionStorage); - - // Add some items that will be removed. - setItemLater('b', item); - - // Do this a little later so that this item is newer. - setItemLater('c', item); - - // Cap the storage at its current size. - storageSizeLimit = sessionStorage.getStubbedSize(); - sessionStorage.setStubbedSizeLimit(storageSizeLimit); - }); - - afterEach(() => { - // Stop controlling time. - fakeTimer.restore(); - }); - - describe('and the item will fit', () => { - it('removes older items until the new item fits', () => { - setItemLater(hash, item); - expect(sessionStorage.getItem('b')).to.equal(null); - expect(sessionStorage.getItem('c')).to.equal(item); - }); - - it('persists the item in sessionStorage', () => { - setItemLater(hash, item); - expect(sessionStorage.getItem(hash)).to.equal(item); - }); - - it('returns true', () => { - const result = setItemLater(hash, item); - expect(result).to.equal(true); - }); - }); - - describe(`and the item won't fit`, () => { - let itemTooBigToFit; - - beforeEach(() => { - // Make sure the item is longer than the storage size limit. - itemTooBigToFit = ''; - const length = storageSizeLimit + 1; - for (let i = 0; i < length; i++) { - itemTooBigToFit += 'a'; - } - }); - - it('removes all items', () => { - setItemLater(hash, itemTooBigToFit); - expect(sessionStorage.getItem('b')).to.equal(null); - expect(sessionStorage.getItem('c')).to.equal(null); - }); - - it(`doesn't persist the item in sessionStorage`, () => { - setItemLater(hash, itemTooBigToFit); - expect(sessionStorage.getItem(hash)).to.equal(null); - }); - - it('returns false', () => { - const result = setItemLater(hash, itemTooBigToFit); - expect(result).to.equal(false); - }); - }); - }); - }); - }); - - describe('#getItem', () => { - describe('if the item exists in sessionStorage', () => { - let fakeTimer; - let sessionStorage; - let hashedItemStore; - - function setItemLater(hash, item) { - // Move time forward, so this item will be "touched" most recently. - fakeTimer.tick(1); - return hashedItemStore.setItem(hash, item); - } - - function getItemLater(hash) { - // Move time forward, so this item will be "touched" most recently. - fakeTimer.tick(1); - return hashedItemStore.getItem(hash); - } - - beforeEach(() => { - // Control time. - fakeTimer = sinon.useFakeTimers(Date.now()); - - sessionStorage = new StubBrowserStorage(); - hashedItemStore = new HashedItemStore(sessionStorage); - hashedItemStore.setItem('1', 'a'); - }); - - afterEach(() => { - // Stop controlling time. - fakeTimer.restore(); - }); - - it('returns the item', () => { - const retrievedItem = hashedItemStore.getItem('1'); - expect(retrievedItem).to.be('a'); - }); - - it('prevents the item from being first to be removed when freeing up storage space', () => { - // Do this a little later so that this item is newer. - setItemLater('2', 'b'); - - // Wait a bit, then retrieve/touch the first item, making *it* newer, and 2 as the oldest. - getItemLater('1'); - - // Cap the storage at its current size. - const storageSizeLimit = sessionStorage.getStubbedSize(); - sessionStorage.setStubbedSizeLimit(storageSizeLimit); - - // Add a new item, causing the second item to be removed, but not the first. - setItemLater('3', 'c'); - expect(hashedItemStore.getItem('2')).to.equal(null); - expect(hashedItemStore.getItem('1')).to.equal('a'); - }); - }); - - describe(`if the item doesn't exist in sessionStorage`, () => { - let sessionStorage; - let hashedItemStore; - const hash = 'a'; - - beforeEach(() => { - sessionStorage = new StubBrowserStorage(); - hashedItemStore = new HashedItemStore(sessionStorage); - }); - - it('returns null', () => { - const retrievedItem = hashedItemStore.getItem(hash); - expect(retrievedItem).to.be(null); - }); - }); - }); - }); - - describe('behavior', () => { - let fakeTimer; - let sessionStorage; - let hashedItemStore; - - function setItemLater(hash, item) { - // Move time forward, so this item will be "touched" most recently. - fakeTimer.tick(1); - return hashedItemStore.setItem(hash, item); - } - - function getItemLater(hash) { - // Move time forward, so this item will be "touched" most recently. - fakeTimer.tick(1); - return hashedItemStore.getItem(hash); - } - - beforeEach(() => { - // Control time. - fakeTimer = sinon.useFakeTimers(Date.now()); - - sessionStorage = new StubBrowserStorage(); - hashedItemStore = new HashedItemStore(sessionStorage); - }); - - afterEach(() => { - // Stop controlling time. - fakeTimer.restore(); - }); - - it('orders items to be removed based on when they were last retrieved', () => { - setItemLater('1', 'a'); - setItemLater('2', 'b'); - setItemLater('3', 'c'); - setItemLater('4', 'd'); - - // Cap the storage at its current size. - const storageSizeLimit = sessionStorage.getStubbedSize(); - sessionStorage.setStubbedSizeLimit(storageSizeLimit); - - // Expect items to be removed in order: 1, 3, 2, 4. - getItemLater('1'); - getItemLater('3'); - getItemLater('2'); - getItemLater('4'); - - setItemLater('5', 'e'); - expect(hashedItemStore.getItem('1')).to.equal(null); - expect(hashedItemStore.getItem('3')).to.equal('c'); - expect(hashedItemStore.getItem('2')).to.equal('b'); - expect(hashedItemStore.getItem('4')).to.equal('d'); - expect(hashedItemStore.getItem('5')).to.equal('e'); - - setItemLater('6', 'f'); - expect(hashedItemStore.getItem('3')).to.equal(null); - expect(hashedItemStore.getItem('2')).to.equal('b'); - expect(hashedItemStore.getItem('4')).to.equal('d'); - expect(hashedItemStore.getItem('5')).to.equal('e'); - expect(hashedItemStore.getItem('6')).to.equal('f'); - - setItemLater('7', 'g'); - expect(hashedItemStore.getItem('2')).to.equal(null); - expect(hashedItemStore.getItem('4')).to.equal('d'); - expect(hashedItemStore.getItem('5')).to.equal('e'); - expect(hashedItemStore.getItem('6')).to.equal('f'); - expect(hashedItemStore.getItem('7')).to.equal('g'); - - setItemLater('8', 'h'); - expect(hashedItemStore.getItem('4')).to.equal(null); - expect(hashedItemStore.getItem('5')).to.equal('e'); - expect(hashedItemStore.getItem('6')).to.equal('f'); - expect(hashedItemStore.getItem('7')).to.equal('g'); - expect(hashedItemStore.getItem('8')).to.equal('h'); - - setItemLater('9', 'i'); - expect(hashedItemStore.getItem('5')).to.equal(null); - expect(hashedItemStore.getItem('6')).to.equal('f'); - expect(hashedItemStore.getItem('7')).to.equal('g'); - expect(hashedItemStore.getItem('8')).to.equal('h'); - expect(hashedItemStore.getItem('9')).to.equal('i'); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/state_storage/__tests__/state_hash.js b/src/legacy/ui/public/state_management/state_storage/__tests__/state_hash.js deleted file mode 100644 index 71fcf33274fc65..00000000000000 --- a/src/legacy/ui/public/state_management/state_storage/__tests__/state_hash.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 { encode as encodeRison } from 'rison-node'; - -import { - createStateHash, - isStateHash, -} from '../state_hash'; - -describe('stateHash', () => { - const existingJsonProvider = () => null; - - describe('#createStateHash', () => { - - describe('returns a hash', () => { - const json = JSON.stringify({ a: 'a' }); - const hash = createStateHash(json, existingJsonProvider); - expect(isStateHash(hash)).to.be(true); - }); - - describe('returns the same hash for the same input', () => { - const json = JSON.stringify({ a: 'a' }); - const hash1 = createStateHash(json, existingJsonProvider); - const hash2 = createStateHash(json, existingJsonProvider); - expect(hash1).to.equal(hash2); - }); - - describe('returns a different hash for different input', () => { - const json1 = JSON.stringify({ a: 'a' }); - const hash1 = createStateHash(json1, existingJsonProvider); - - const json2 = JSON.stringify({ a: 'b' }); - const hash2 = createStateHash(json2, existingJsonProvider); - expect(hash1).to.not.equal(hash2); - }); - }); - - describe('#isStateHash', () => { - it('returns true for values created using #createStateHash', () => { - const json = JSON.stringify({ a: 'a' }); - const hash = createStateHash(json, existingJsonProvider); - expect(isStateHash(hash)).to.be(true); - }); - - it('returns false for values not created using #createStateHash', () => { - const json = JSON.stringify({ a: 'a' }); - expect(isStateHash(json)).to.be(false); - }); - - it('returns false for RISON', () => { - // We're storing RISON in the URL, so let's test against this specifically. - const rison = encodeRison({ a: 'a' }); - expect(isStateHash(rison)).to.be(false); - }); - }); -}); diff --git a/src/legacy/ui/public/state_management/state_storage/hashed_item_store.js b/src/legacy/ui/public/state_management/state_storage/hashed_item_store.js deleted file mode 100644 index 71306524160722..00000000000000 --- a/src/legacy/ui/public/state_management/state_storage/hashed_item_store.js +++ /dev/null @@ -1,170 +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 HashedItemStore associates JSON objects with states in browser history and persists these - * objects in sessionStorage. We persist them so that when a tab is closed and re-opened, we can - * retain access to the state objects referenced by the browser history. - * - * Because there is a limit on how much data we can put into sessionStorage, the HashedItemStore - * will attempt to remove old items from storage once that limit is reached. - * - * ------------------------------------------------------------------------------------------------- - * - * Consideration 1: We can't (easily) mirror the browser history - * - * If we use letters to indicate a unique state object, and numbers to represent the same state - * occurring again (due to action by the user), a history could look like this: - * - * Old < - - - - - - - - > New - * A1 | B1 | C1 | A2 | D1 | E1 - * - * If the user navigates back to C1 and starts to create new states, persisted history states will - * become inaccessible: - * - * Old < - - - - - - - - - - -> New - * A1 | B1 | C1 | F1 | G1 | H1 | I1 (new history states) - * A2 | D1 | E1 (inaccessible persisted history states) - * - * Theoretically, we could build a mirror of the browser history. When the onpopstate event is - * dispatched, we could determine whether we have gone back or forward in history. Then, when - * a new state is persisted, we could delete all of the persisted items which are no longer - * accessible. (Note that this would require reference-counting so that A isn't removed while D and - * E are, since A would still have a remaining reference from A1). - * - * However, the History API doesn't allow us to read from the history beyond the current state. This - * means that if a session is restored, we can't rebuild this browser history mirror. - * - * Due to this imperfect implementation, HashedItemStore ignores the possibility of inaccessible - * history states. In the future, we could implement this history mirror and persist it in - * sessionStorage too. Then, when restoring a session, we can just retrieve it from sessionStorage. - * - * ------------------------------------------------------------------------------------------------- - * - * Consideration 2: We can't tell when we've hit the browser history limit - * - * Because some of our persisted history states may no longer be referenced by the browser history, - * and we have no way of knowing which ones, we have no way of knowing whether we've persisted a - * number of accessible states beyond the browser history length limit. - * - * More fundamentally, the browser history length limit is a browser implementation detail, so it - * can change from browser to browser, or over time. Respecting this limit would introduce a lot of - * (unnecessary?) complexity. - * - * For these reasons, HashedItemStore doesn't concern itself with this constraint. - */ - -import { pull, sortBy } from 'lodash'; - -export class HashedItemStore { - - /** - * HashedItemStore uses objects called indexed items to refer to items that have been persisted - * in sessionStorage. An indexed item is shaped {hash, touched}. The touched date is when the item - * was last referenced by the browser history. - */ - constructor(sessionStorage) { - this._sessionStorage = sessionStorage; - - // Store indexed items in descending order by touched (oldest first, newest last). We'll use - // this to remove older items when we run out of storage space. - this._indexedItems = []; - - // Potentially restore a previously persisted index. This happens when - // we re-open a closed tab. - const persistedItemIndex = this._sessionStorage.getItem(HashedItemStore.PERSISTED_INDEX_KEY); - if (persistedItemIndex) { - this._indexedItems = sortBy(JSON.parse(persistedItemIndex) || [], 'touched'); - } - } - - setItem(hash, item) { - const isItemPersisted = this._persistItem(hash, item); - - if (isItemPersisted) { - this._touchHash(hash); - } - - return isItemPersisted; - } - - getItem(hash) { - const item = this._sessionStorage.getItem(hash); - - if (item !== null) { - this._touchHash(hash); - } - - return item; - } - - _getIndexedItem(hash) { - return this._indexedItems.find(indexedItem => indexedItem.hash === hash); - } - - _persistItem(hash, item) { - try { - this._sessionStorage.setItem(hash, item); - return true; - } catch (e) { - // If there was an error then we need to make some space for the item. - if (this._indexedItems.length === 0) { - // If there's nothing left to remove, then we've run out of space and we're trying to - // persist too large an item. - return false; - } - - // We need to try to make some space for the item by removing older items (i.e. items that - // haven't been accessed recently). - this._removeOldestItem(); - - // Try to persist again. - return this._persistItem(hash, item); - } - } - - _removeOldestItem() { - const oldestIndexedItem = this._indexedItems.shift(); - // Remove oldest item from storage. - this._sessionStorage.removeItem(oldestIndexedItem.hash); - } - - _touchHash(hash) { - // Touching a hash indicates that it's been used recently, so it won't be the first in line - // when we remove items to free up storage space. - - // either get or create an indexedItem - const indexedItem = this._getIndexedItem(hash) || { hash }; - - // set/update the touched time to now so that it's the "newest" item in the index - indexedItem.touched = Date.now(); - - // ensure that the item is last in the index - pull(this._indexedItems, indexedItem); - this._indexedItems.push(indexedItem); - - // Regardless of whether this is a new or updated item, we need to persist the index. - this._sessionStorage.setItem( - HashedItemStore.PERSISTED_INDEX_KEY, - JSON.stringify(this._indexedItems) - ); - } -} - -HashedItemStore.PERSISTED_INDEX_KEY = 'kbn.hashedItemsIndex.v1'; diff --git a/src/legacy/ui/public/state_management/state_storage/hashed_item_store_singleton.js b/src/legacy/ui/public/state_management/state_storage/hashed_item_store_singleton.js deleted file mode 100644 index 234559c95ebf79..00000000000000 --- a/src/legacy/ui/public/state_management/state_storage/hashed_item_store_singleton.js +++ /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 { HashedItemStore } from './hashed_item_store'; - -export const HashedItemStoreSingleton = new HashedItemStore(window.sessionStorage); diff --git a/src/legacy/ui/public/state_management/state_storage/index.js b/src/legacy/ui/public/state_management/state_storage/index.js deleted file mode 100644 index eb878fc88b5787..00000000000000 --- a/src/legacy/ui/public/state_management/state_storage/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 { HashedItemStoreSingleton } from './hashed_item_store_singleton'; - -export { - createStateHash, - isStateHash, -} from './state_hash'; diff --git a/src/legacy/ui/public/state_management/state_storage/state_hash.js b/src/legacy/ui/public/state_management/state_storage/state_hash.js deleted file mode 100644 index 0e8dc2c05c0f28..00000000000000 --- a/src/legacy/ui/public/state_management/state_storage/state_hash.js +++ /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 { Sha256 } from '../../../../../core/public/utils/'; - -// This prefix is used to identify hash strings that have been encoded in the URL. -const HASH_PREFIX = 'h@'; - -export function createStateHash(json, existingJsonProvider) { - if (typeof json !== 'string') { - throw new Error('createHash only accepts strings (JSON).'); - } - - const hash = new Sha256().update(json, 'utf8').digest('hex'); - - let shortenedHash; - - // Shorten the hash to at minimum 7 characters. We just need to make sure that it either: - // a) hasn't been used yet - // b) or has been used already, but with the JSON we're currently hashing. - for (let i = 7; i < hash.length; i++) { - shortenedHash = hash.slice(0, i); - const existingJson = existingJsonProvider(shortenedHash); - if (existingJson === null || existingJson === json) break; - } - - return `${HASH_PREFIX}${shortenedHash}`; -} - -export function isStateHash(str) { - return String(str).indexOf(HASH_PREFIX) === 0; -} diff --git a/src/legacy/ui/public/styles/_legacy/_mixins.scss b/src/legacy/ui/public/styles/_legacy/_mixins.scss index b063aa763838b2..2834f605550708 100644 --- a/src/legacy/ui/public/styles/_legacy/_mixins.scss +++ b/src/legacy/ui/public/styles/_legacy/_mixins.scss @@ -3,8 +3,6 @@ // DO NOT CONTINUE TO USE THESE MIXINS -@import '@elastic/eui/src/components/form/variables'; - @mixin __legacyInputStyles__bad { &:not([type='range']) { appearance: none; diff --git a/src/legacy/ui/public/styles/_legacy/components/_ui_select.scss b/src/legacy/ui/public/styles/_legacy/components/_ui_select.scss index 50ec20f0161946..691ec17b5b9673 100644 --- a/src/legacy/ui/public/styles/_legacy/components/_ui_select.scss +++ b/src/legacy/ui/public/styles/_legacy/components/_ui_select.scss @@ -1,6 +1,3 @@ -@import '@elastic/eui/src/components/button/variables'; -@import '@elastic/eui/src/components/form/variables'; - /*! * ui-select * http://github.com/angular-ui/ui-select @@ -8,7 +5,6 @@ * License: MIT */ - /* Style when highlighting a search. */ .ui-select-highlight { font-weight: bold; @@ -43,12 +39,12 @@ padding-left: 0; } -.select2-locked > .select2-search-choice-close{ - display:none; +.select2-locked > .select2-search-choice-close { + display: none; } -.select-locked > .ui-select-match-close{ - display:none; +.select-locked > .ui-select-match-close { + display: none; } body > .select2-container.open { @@ -56,42 +52,42 @@ body > .select2-container.open { } /* Handle up direction Select2 */ -.ui-select-container[theme="select2"].direction-up .ui-select-match, +.ui-select-container[theme='select2'].direction-up .ui-select-match, .ui-select-container.select2.direction-up .ui-select-match { - border-radius: 4px; /* FIXME hardcoded value :-/ */ - border-top-left-radius: 0; - border-top-right-radius: 0; + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-top-left-radius: 0; + border-top-right-radius: 0; } -.ui-select-container[theme="select2"].direction-up .ui-select-dropdown, +.ui-select-container[theme='select2'].direction-up .ui-select-dropdown, .ui-select-container.select2.direction-up .ui-select-dropdown { - border-radius: 4px; /* FIXME hardcoded value :-/ */ - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + border-radius: 4px; /* FIXME hardcoded value :-/ */ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; - border-top-width: 1px; /* FIXME hardcoded value :-/ */ - border-top-style: solid; + border-top-width: 1px; /* FIXME hardcoded value :-/ */ + border-top-style: solid; - box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); + box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25); - margin-top: -4px; /* FIXME hardcoded value :-/ */ + margin-top: -4px; /* FIXME hardcoded value :-/ */ } -.ui-select-container[theme="select2"].direction-up .ui-select-dropdown .select2-search, +.ui-select-container[theme='select2'].direction-up .ui-select-dropdown .select2-search, .ui-select-container.select2.direction-up .ui-select-dropdown .select2-search { - margin-top: 4px; /* FIXME hardcoded value :-/ */ + margin-top: 4px; /* FIXME hardcoded value :-/ */ } -.ui-select-container[theme="select2"].direction-up.select2-dropdown-open .ui-select-match, +.ui-select-container[theme='select2'].direction-up.select2-dropdown-open .ui-select-match, .ui-select-container.select2.direction-up.select2-dropdown-open .ui-select-match { - border-bottom-color: $euiColorPrimary; + border-bottom-color: $euiColorPrimary; } -.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden, -.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden input{ - opacity: 0; - height: 0; - min-height: 0; - padding: 0; - margin: 0; - border:0; +.ui-select-container[theme='select2'] .ui-select-dropdown .ui-select-search-hidden, +.ui-select-container[theme='select2'] .ui-select-dropdown .ui-select-search-hidden input { + opacity: 0; + height: 0; + min-height: 0; + padding: 0; + margin: 0; + border: 0; } /* Bootstrap theme */ @@ -133,12 +129,12 @@ body > .select2-container.open { } .ui-select-bootstrap .ui-select-search-hidden { - opacity: 0; - height: 0; - min-height: 0; - padding: 0; - margin: 0; - border:0; + opacity: 0; + height: 0; + min-height: 0; + padding: 0; + margin: 0; + border: 0; } .ui-select-bootstrap > .ui-select-match > .btn { @@ -213,7 +209,7 @@ body > .ui-select-bootstrap.open { } .ui-select-multiple:hover .ui-select-match-item.dropping-before:before { - content: ""; + content: ''; position: absolute; top: 0; right: 100%; @@ -223,7 +219,7 @@ body > .ui-select-bootstrap.open { } .ui-select-multiple:hover .ui-select-match-item.dropping-after:after { - content: ""; + content: ''; position: absolute; top: 0; left: 100%; @@ -232,7 +228,7 @@ body > .ui-select-bootstrap.open { border-right: 1px solid $euiColorPrimary; } -.ui-select-bootstrap .ui-select-choices-row>span { +.ui-select-bootstrap .ui-select-choices-row > span { @include euiFontSizeS; @include euiTextTruncate; font-weight: inherit; @@ -251,45 +247,44 @@ body > .ui-select-bootstrap.open { } } - -.ui-select-bootstrap .ui-select-choices-row.active>span { - color: $euiTextColor; - text-decoration: none; - outline: 0; - background-color: $euiFocusBackgroundColor; +.ui-select-bootstrap .ui-select-choices-row.active > span { + color: $euiTextColor; + text-decoration: none; + outline: 0; + background-color: $euiFocusBackgroundColor; } -.ui-select-bootstrap .ui-select-choices-row.disabled>span, -.ui-select-bootstrap .ui-select-choices-row.active.disabled>span { - color: $euiButtonColorDisabled; - cursor: not-allowed; - background-color: transparent; +.ui-select-bootstrap .ui-select-choices-row.disabled > span, +.ui-select-bootstrap .ui-select-choices-row.active.disabled > span { + color: $euiButtonColorDisabled; + cursor: not-allowed; + background-color: transparent; } /* fix hide/show angular animation */ .ui-select-match.ng-hide-add, .ui-select-search.ng-hide-add { - display: none !important; + display: none !important; } /* Mark invalid Bootstrap */ .ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match { - border-color: $euiColorDanger; + border-color: $euiColorDanger; } /* Handle up direction Bootstrap */ -.ui-select-container[theme="bootstrap"].direction-up .ui-select-dropdown { +.ui-select-container[theme='bootstrap'].direction-up .ui-select-dropdown { @include euiBottomShadowMedium; } .ui-select-bootstrap .ui-select-match-text { - width: 100%; - padding-right: 1em; + width: 100%; + padding-right: 1em; } .ui-select-bootstrap .ui-select-match-text span { - display: inline-block; - width: 100%; - overflow: hidden; + display: inline-block; + width: 100%; + overflow: hidden; } .ui-select-bootstrap .ui-select-toggle > a.btn { position: absolute; @@ -300,17 +295,17 @@ body > .ui-select-bootstrap.open { /* Spinner */ .ui-select-refreshing { - position: absolute; - right: 0; - padding: 8px 27px; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - -webkit-font-smoothing:antialiased; - } + position: absolute; + right: 0; + padding: 8px 27px; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; +} @-webkit-keyframes ui-select-spin { 0% { @@ -342,7 +337,6 @@ body > .ui-select-bootstrap.open { -webkit-animation: none 0s; } - // Other Custom /** diff --git a/src/legacy/ui/public/test_harness/test_harness.js b/src/legacy/ui/public/test_harness/test_harness.js index 8c58ca4e0ad036..fa7ca0dd62ac1e 100644 --- a/src/legacy/ui/public/test_harness/test_harness.js +++ b/src/legacy/ui/public/test_harness/test_harness.js @@ -21,9 +21,11 @@ import chrome from '../chrome'; import { parse as parseUrl } from 'url'; +import { Subject } from 'rxjs'; import sinon from 'sinon'; import { metadata } from '../metadata'; -import { UiSettingsClient } from '../../../../core/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { UiSettingsClient } from '../../../../core/public/ui_settings'; import './test_harness.css'; import 'ng_mock'; @@ -46,10 +48,12 @@ before(() => { }); let stubUiSettings; +let done$; function createStubUiSettings() { if (stubUiSettings) { - stubUiSettings.stop(); + done$.complete(); } + done$ = new Subject(); stubUiSettings = new UiSettingsClient({ api: { @@ -60,6 +64,7 @@ function createStubUiSettings() { onUpdateError: () => {}, defaults: metadata.uiSettings.defaults, initialSettings: {}, + done$, }); } diff --git a/src/legacy/ui/public/time_buckets/index.d.ts b/src/legacy/ui/public/time_buckets/index.d.ts new file mode 100644 index 00000000000000..70b9495b81f0eb --- /dev/null +++ b/src/legacy/ui/public/time_buckets/index.d.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. + */ + +declare module 'ui/time_buckets' { + export const TimeBuckets: any; +} diff --git a/src/legacy/ui/public/utils/__tests__/base_object.js b/src/legacy/ui/public/utils/__tests__/base_object.js deleted file mode 100644 index dfc5688c7b2f4f..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/base_object.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 '../../private'; - -import { BaseObject } from '../base_object'; - -describe('Base Object', function () { - beforeEach(ngMock.module('kibana')); - - it('should take an inital set of values', function () { - const baseObject = new BaseObject({ message: 'test' }); - expect(baseObject).to.have.property('message', 'test'); - }); - - it('should serialize attributes to RISON', function () { - const baseObject = new BaseObject(); - baseObject.message = 'Testing... 1234'; - const rison = baseObject.toRISON(); - expect(rison).to.equal('(message:\'Testing... 1234\')'); - }); - - it('should not serialize $$attributes to RISON', function () { - const baseObject = new BaseObject(); - baseObject.$$attributes = { foo: 'bar' }; - baseObject.message = 'Testing... 1234'; - const rison = baseObject.toRISON(); - expect(rison).to.equal('(message:\'Testing... 1234\')'); - }); - - it('should serialize attributes for JSON', function () { - const baseObject = new BaseObject(); - baseObject.message = 'Testing... 1234'; - baseObject._private = 'foo'; - baseObject.$private = 'stuff'; - const json = JSON.stringify(baseObject); - expect(json).to.equal('{"message":"Testing... 1234"}'); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js b/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js deleted file mode 100644 index 1ffe9ca7b4df26..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js +++ /dev/null @@ -1,43 +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 { geohashColumns, decodeGeoHash } from '../decode_geo_hash'; - -test('geohashColumns', function () { - expect(geohashColumns(1)).toBe(8); - expect(geohashColumns(2)).toBe(8 * 4); - expect(geohashColumns(3)).toBe(8 * 4 * 8); - expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4); -}); - -test('decodeGeoHash', function () { - expect(decodeGeoHash('drm3btev3e86')).toEqual({ - latitude: [ - 41.119999922811985, - 41.12000009045005, - 41.12000000663102, - ], - longitude: [ - -71.34000029414892, - -71.3399999588728, - -71.34000012651086, - ], - }); -}); - diff --git a/src/legacy/ui/public/utils/__tests__/range.js b/src/legacy/ui/public/utils/__tests__/range.js deleted file mode 100644 index e7947894d3e221..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/range.js +++ /dev/null @@ -1,129 +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 { parseRange } from '../range'; - -describe('Range parsing utility', function () { - - it('throws an error for inputs that are not formatted properly', function () { - expect(function () { - parseRange(''); - }).to.throwException(TypeError); - - expect(function () { - parseRange('p10202'); - }).to.throwException(TypeError); - - expect(function () { - parseRange('{0,100}'); - }).to.throwException(TypeError); - - expect(function () { - parseRange('[0,100'); - }).to.throwException(TypeError); - - expect(function () { - parseRange(')0,100('); - }).to.throwException(TypeError); - }); - - const tests = { - '[ 0 , 100 ]': { - props: { - min: 0, - max: 100, - minInclusive: true, - maxInclusive: true - }, - within: [ - [0, true], - [0.0000001, true], - [1, true], - [99.99999, true], - [100, true] - ] - }, - '(26.3 , 42]': { - props: { - min: 26.3, - max: 42, - minInclusive: false, - maxInclusive: true - }, - within: [ - [26.2999999, false], - [26.3000001, true], - [30, true], - [41, true], - [42, true] - ] - }, - '(-50,50)': { - props: { - min: -50, - max: 50, - minInclusive: false, - maxInclusive: false - }, - within: [ - [-50, false], - [-49.99999, true], - [0, true], - [49.99999, true], - [50, false] - ] - }, - '(Infinity, -Infinity)': { - props: { - min: -Infinity, - max: Infinity, - minInclusive: false, - maxInclusive: false - }, - within: [ - [0, true], - [-0.0000001, true], - [-1, true], - [-10000000000, true], - [-Infinity, false], - [Infinity, false], - ] - } - }; - - _.forOwn(tests, function (spec, str) { - - describe(str, function () { - const range = parseRange(str); - - it('creation', function () { - expect(range).to.eql(spec.props); - }); - - spec.within.forEach(function (tup) { - it('#within(' + tup[0] + ')', function () { - expect(range.within(tup[0])).to.be(tup[1]); - }); - }); - }); - - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/simple_emitter.js b/src/legacy/ui/public/utils/__tests__/simple_emitter.js deleted file mode 100644 index 25224a409f8f4b..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/simple_emitter.js +++ /dev/null @@ -1,175 +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 { SimpleEmitter } from '../simple_emitter'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -describe('SimpleEmitter class', function () { - let emitter; - - beforeEach(function () { - emitter = new SimpleEmitter(); - }); - - it('constructs an event emitter', function () { - expect(emitter).to.have.property('on'); - expect(emitter).to.have.property('off'); - expect(emitter).to.have.property('emit'); - expect(emitter).to.have.property('listenerCount'); - expect(emitter).to.have.property('removeAllListeners'); - }); - - describe('#listenerCount', function () { - it('counts all event listeners without any arg', function () { - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', function () {}); - expect(emitter.listenerCount()).to.be(1); - emitter.on('b', function () {}); - expect(emitter.listenerCount()).to.be(2); - }); - - it('limits to the event that is passed in', function () { - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(1); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - emitter.on('b', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - expect(emitter.listenerCount('b')).to.be(1); - expect(emitter.listenerCount()).to.be(3); - }); - }); - - describe('#on', function () { - it('registers a handler', function () { - const handler = sinon.stub(); - emitter.on('a', handler); - expect(emitter.listenerCount('a')).to.be(1); - - expect(handler.callCount).to.be(0); - emitter.emit('a'); - expect(handler.callCount).to.be(1); - }); - - it('allows multiple event handlers for the same event', function () { - emitter.on('a', function () {}); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - }); - - it('allows the same function to be registered multiple times', function () { - const handler = function () {}; - emitter.on('a', handler); - expect(emitter.listenerCount()).to.be(1); - emitter.on('a', handler); - expect(emitter.listenerCount()).to.be(2); - }); - }); - - describe('#off', function () { - it('removes a listener if it was registered', function () { - const handler = sinon.stub(); - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', handler); - expect(emitter.listenerCount('a')).to.be(1); - emitter.off('a', handler); - expect(emitter.listenerCount('a')).to.be(0); - }); - - it('clears all listeners if no handler is passed', function () { - emitter.on('a', function () {}); - emitter.on('a', function () {}); - expect(emitter.listenerCount()).to.be(2); - emitter.off('a'); - expect(emitter.listenerCount()).to.be(0); - }); - - it('does not mind if the listener is not registered', function () { - emitter.off('a', function () {}); - }); - - it('does not mind if the event has no listeners', function () { - emitter.off('a'); - }); - }); - - describe('#emit', function () { - it('calls the handlers in the order they were defined', function () { - let i = 0; - const incr = function () { return ++i; }; - const one = sinon.spy(incr); - const two = sinon.spy(incr); - const three = sinon.spy(incr); - const four = sinon.spy(incr); - - emitter - .on('a', one) - .on('a', two) - .on('a', three) - .on('a', four) - .emit('a'); - - expect(one).to.have.property('callCount', 1); - expect(one.returned(1)).to.be.ok(); - - expect(two).to.have.property('callCount', 1); - expect(two.returned(2)).to.be.ok(); - - expect(three).to.have.property('callCount', 1); - expect(three.returned(3)).to.be.ok(); - - expect(four).to.have.property('callCount', 1); - expect(four.returned(4)).to.be.ok(); - }); - - it('always emits the handlers that were initially registered', function () { - - const destructive = sinon.spy(function () { - emitter.removeAllListeners(); - expect(emitter.listenerCount()).to.be(0); - }); - const stub = sinon.stub(); - - emitter.on('run', destructive).on('run', stub).emit('run'); - - expect(destructive).to.have.property('callCount', 1); - expect(stub).to.have.property('callCount', 1); - }); - - it('applies all arguments except the first', function () { - emitter - .on('a', function (a, b, c) { - expect(a).to.be('foo'); - expect(b).to.be('bar'); - expect(c).to.be('baz'); - }) - .emit('a', 'foo', 'bar', 'baz'); - }); - - it('uses the SimpleEmitter as the this context', function () { - emitter - .on('a', function () { - expect(this).to.be(emitter); - }) - .emit('a'); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/base_object.ts b/src/legacy/ui/public/utils/base_object.ts deleted file mode 100644 index 63c7ebf6de5bb6..00000000000000 --- a/src/legacy/ui/public/utils/base_object.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 angular from 'angular'; -import _ from 'lodash'; -// @ts-ignore -- awaiting https://github.com/w33ble/rison-node/issues/1 -import rison from 'rison-node'; - -export class BaseObject { - // Set the attributes or default to an empty object - constructor(attributes: Record = {}) { - // Set the attributes or default to an empty object - _.assign(this, attributes); - } - - public toObject() { - // return just the data. - return _.omit(this, (value: any, key: string) => { - return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); - }); - } - - public toRISON() { - // Use Angular to remove the private vars, and JSON.stringify to serialize - return rison.encode(JSON.parse(angular.toJson(this))); - } - - public toJSON() { - return this.toObject(); - } -} diff --git a/src/legacy/ui/public/utils/find_by_param.ts b/src/legacy/ui/public/utils/find_by_param.ts deleted file mode 100644 index de32fc955a8cd3..00000000000000 --- a/src/legacy/ui/public/utils/find_by_param.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 _ from 'lodash'; - -interface AnyObject { - [key: string]: any; -} - -// given an object or array of objects, return the value of the passed param -// if the param is missing, return undefined -export function findByParam(values: AnyObject | AnyObject[], param: string) { - if (Array.isArray(values)) { - // point series chart - const index = _.findIndex(values, param); - if (index === -1) { - return; - } - return values[index][param]; - } - return values[param]; // pie chart -} diff --git a/src/legacy/ui/public/utils/range.d.ts b/src/legacy/ui/public/utils/range.d.ts deleted file mode 100644 index c484c6f43eebba..00000000000000 --- a/src/legacy/ui/public/utils/range.d.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 function parseRange(input: string): Range; - -export interface Range { - min: number; - max: number; - minInclusive: boolean; - maxInclusive: boolean; - within(n: number): boolean; -} diff --git a/src/legacy/ui/public/utils/range.js b/src/legacy/ui/public/utils/range.js deleted file mode 100644 index 54bd1b19033469..00000000000000 --- a/src/legacy/ui/public/utils/range.js +++ /dev/null @@ -1,84 +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'; - -/** - * Regexp portion that matches our number - * - * supports: - * -100 - * -100.0 - * 0 - * 0.10 - * Infinity - * -Infinity - * - * @type {String} - */ -const _RE_NUMBER = '(\\-?(?:\\d+(?:\\.\\d+)?|Infinity))'; - -/** - * Regexp for the interval notation - * - * supports: - * [num, num] - * ( num , num ] - * [Infinity,num) - * - * @type {RegExp} - */ -const RANGE_RE = new RegExp('^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$'); - -export function parseRange(input) { - - const match = String(input).match(RANGE_RE); - if (!match) { - throw new TypeError('expected input to be in interval notation e.g., (100, 200]'); - } - - return new Range( - match[1] === '[', - parseFloat(match[2]), - parseFloat(match[3]), - match[4] === ']' - ); -} - -function Range(/* minIncl, min, max, maxIncl */) { - const args = _.toArray(arguments); - if (args[1] > args[2]) args.reverse(); - - this.minInclusive = args[0]; - this.min = args[1]; - this.max = args[2]; - this.maxInclusive = args[3]; -} - -Range.prototype.within = function (n) { - if (this.min === n && !this.minInclusive) return false; - if (this.min > n) return false; - - if (this.max === n && !this.maxInclusive) return false; - if (this.max < n) return false; - - return true; -}; - - diff --git a/src/legacy/ui/public/utils/simple_emitter.js b/src/legacy/ui/public/utils/simple_emitter.js index 84397962c286b0..503798ba160dbb 100644 --- a/src/legacy/ui/public/utils/simple_emitter.js +++ b/src/legacy/ui/public/utils/simple_emitter.js @@ -18,8 +18,6 @@ */ import _ from 'lodash'; -import { BaseObject } from './base_object'; -import { createLegacyClass } from './legacy_class'; /** * Simple event emitter class used in the vislib. Calls @@ -27,7 +25,6 @@ import { createLegacyClass } from './legacy_class'; * * @class */ -createLegacyClass(SimpleEmitter).inherits(BaseObject); export function SimpleEmitter() { this._listeners = {}; } @@ -134,4 +131,3 @@ SimpleEmitter.prototype.listenerCount = function (name) { return count + _.size(handlers); }, 0); }; - diff --git a/src/legacy/ui/public/utils/simple_emitter.test.js b/src/legacy/ui/public/utils/simple_emitter.test.js new file mode 100644 index 00000000000000..ff884a12be7ee8 --- /dev/null +++ b/src/legacy/ui/public/utils/simple_emitter.test.js @@ -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 { SimpleEmitter } from './simple_emitter'; +import sinon from 'sinon'; + +describe('SimpleEmitter class', () => { + let emitter; + + beforeEach(() => { + emitter = new SimpleEmitter(); + }); + + it('constructs an event emitter', () => { + expect(emitter).toHaveProperty('on'); + expect(emitter).toHaveProperty('off'); + expect(emitter).toHaveProperty('emit'); + expect(emitter).toHaveProperty('listenerCount'); + expect(emitter).toHaveProperty('removeAllListeners'); + }); + + describe('#listenerCount', () => { + it('counts all event listeners without any arg', () => { + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', () => {}); + expect(emitter.listenerCount()).toBe(1); + emitter.on('b', () => {}); + expect(emitter.listenerCount()).toBe(2); + }); + + it('limits to the event that is passed in', () => { + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(1); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + emitter.on('b', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + expect(emitter.listenerCount('b')).toBe(1); + expect(emitter.listenerCount()).toBe(3); + }); + }); + + describe('#on', () => { + it('registers a handler', () => { + const handler = sinon.stub(); + emitter.on('a', handler); + expect(emitter.listenerCount('a')).toBe(1); + + expect(handler.callCount).toBe(0); + emitter.emit('a'); + expect(handler.callCount).toBe(1); + }); + + it('allows multiple event handlers for the same event', () => { + emitter.on('a', () => {}); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + }); + + it('allows the same function to be registered multiple times', () => { + const handler = () => {}; + emitter.on('a', handler); + expect(emitter.listenerCount()).toBe(1); + emitter.on('a', handler); + expect(emitter.listenerCount()).toBe(2); + }); + }); + + describe('#off', () => { + it('removes a listener if it was registered', () => { + const handler = sinon.stub(); + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', handler); + expect(emitter.listenerCount('a')).toBe(1); + emitter.off('a', handler); + expect(emitter.listenerCount('a')).toBe(0); + }); + + it('clears all listeners if no handler is passed', () => { + emitter.on('a', () => {}); + emitter.on('a', () => {}); + expect(emitter.listenerCount()).toBe(2); + emitter.off('a'); + expect(emitter.listenerCount()).toBe(0); + }); + + it('does not mind if the listener is not registered', () => { + emitter.off('a', () => {}); + }); + + it('does not mind if the event has no listeners', () => { + emitter.off('a'); + }); + }); + + describe('#emit', () => { + it('calls the handlers in the order they were defined', () => { + let i = 0; + const incr = () => ++i; + const one = sinon.spy(incr); + const two = sinon.spy(incr); + const three = sinon.spy(incr); + const four = sinon.spy(incr); + + emitter + .on('a', one) + .on('a', two) + .on('a', three) + .on('a', four) + .emit('a'); + + expect(one).toHaveProperty('callCount', 1); + expect(one.returned(1)).toBeDefined(); + + expect(two).toHaveProperty('callCount', 1); + expect(two.returned(2)).toBeDefined(); + + expect(three).toHaveProperty('callCount', 1); + expect(three.returned(3)).toBeDefined(); + + expect(four).toHaveProperty('callCount', 1); + expect(four.returned(4)).toBeDefined(); + }); + + it('always emits the handlers that were initially registered', () => { + const destructive = sinon.spy(() => { + emitter.removeAllListeners(); + expect(emitter.listenerCount()).toBe(0); + }); + const stub = sinon.stub(); + + emitter.on('run', destructive).on('run', stub).emit('run'); + + expect(destructive).toHaveProperty('callCount', 1); + expect(stub).toHaveProperty('callCount', 1); + }); + + it('applies all arguments except the first', () => { + emitter + .on('a', (a, b, c) => { + expect(a).toBe('foo'); + expect(b).toBe('bar'); + expect(c).toBe('baz'); + }) + .emit('a', 'foo', 'bar', 'baz'); + }); + + it('uses the SimpleEmitter as the this context', () => { + emitter + .on('a', function () { + expect(this).toBe(emitter); + }) + .emit('a'); + }); + }); +}); diff --git a/src/legacy/ui/public/utils/zoom_to_precision.js b/src/legacy/ui/public/utils/zoom_to_precision.js deleted file mode 100644 index f5c16b640d127e..00000000000000 --- a/src/legacy/ui/public/utils/zoom_to_precision.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 { geohashColumns } from './decode_geo_hash'; - -const maxPrecision = 12; -/** - * Map Leaflet zoom levels to geohash precision levels. - * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. - */ - - - - -const zoomPrecisionMap = {}; -const minGeohashPixels = 16; - -function calculateZoomToPrecisionMap(maxZoom) { - - for (let zoom = 0; zoom <= maxZoom; zoom += 1) { - if (typeof zoomPrecisionMap[zoom] === 'number') { - continue; - } - const worldPixels = 256 * Math.pow(2, zoom); - zoomPrecisionMap[zoom] = 1; - for (let precision = 2; precision <= maxPrecision; precision += 1) { - const columns = geohashColumns(precision); - if ((worldPixels / columns) >= minGeohashPixels) { - zoomPrecisionMap[zoom] = precision; - } else { - break; - } - } - } -} - - -export function zoomToPrecision(mapZoom, maxPrecision, maxZoom) { - calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); - return Math.min(zoomPrecisionMap[mapZoom], maxPrecision); -} diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js index 9b0398cf8853e1..559124e20c10a2 100644 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js @@ -463,7 +463,7 @@ describe('AggConfig', function () { }); it('returns the field\'s formatter', function () { - expect(vis.aggs.aggs[0].fieldFormatter()).to.be(vis.aggs.aggs[0].getField().format.getConverterFor()); + expect(vis.aggs.aggs[0].fieldFormatter().toString()).to.be(vis.aggs.aggs[0].getField().format.getConverterFor().toString()); }); it('returns the string format if the field does not have a format', function () { @@ -484,7 +484,7 @@ describe('AggConfig', function () { it('returns the html converter if "html" is passed in', function () { const field = indexPattern.fields.getByName('bytes'); - expect(vis.aggs.aggs[0].fieldFormatter('html')).to.be(field.format.getConverterFor('html')); + expect(vis.aggs.aggs[0].fieldFormatter('html').toString()).to.be(field.format.getConverterFor('html').toString()); }); }); }); diff --git a/src/legacy/ui/public/vis/__tests__/map/kibana_map.js b/src/legacy/ui/public/vis/__tests__/map/kibana_map.js index e67d4b5f7603b2..88718452d919aa 100644 --- a/src/legacy/ui/public/vis/__tests__/map/kibana_map.js +++ b/src/legacy/ui/public/vis/__tests__/map/kibana_map.js @@ -251,7 +251,6 @@ describe('kibana_map tests', function () { kibanaMap.removeLayer(layer); expect(domNode.querySelectorAll('.leaflet-control-attribution')[0].innerHTML).to.equal('foo, bar'); - }); }); @@ -320,6 +319,29 @@ describe('kibana_map tests', function () { }); + it('WMS - should clean attribution', async function () { + + const options = { + url: 'https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer', + version: '1.1.0', + layers: '0', + format: 'image/png', + transparent: true, + attribution: '
foobar
', + styles: '', + minZoom: 1, + maxZoom: 18 + }; + + kibanaMap.setBaseLayer({ + baseLayerType: 'wms', + options: options + }); + + expect(domNode.querySelectorAll('.leaflet-control-attribution')[0].innerHTML).to.equal('<div>foobar</div>'); + + }); + }); diff --git a/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss b/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss index 451ecc80844dde..5a30ded7c2e5bd 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss +++ b/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss @@ -1,6 +1,3 @@ -@import '@elastic/eui/src/components/tool_tip/variables'; -@import '@elastic/eui/src/components/tool_tip/mixins'; - .visTooltip, .visTooltip__sizingClone { @include euiToolTipStyle('s'); diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss index 5a8a0aabad565e..e6b75b1a1f783c 100644 --- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss +++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - // // LAYOUT // @@ -44,7 +42,6 @@ } } - /** * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap. */ @@ -97,7 +94,7 @@ margin-top: $euiSizeS; } - label:not([class^="eui"]) { + label:not([class^='eui']) { @include __legacyLabelStyles__bad; display: block; } @@ -169,7 +166,7 @@ .visEditorSidebar__aggGroupAccordionButtonContent { font-size: $euiFontSizeS; - span { + span { color: $euiColorDarkShade; } } 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 index ab192e6fd3cbb8..4004f8627a8987 100644 --- 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 @@ -18,7 +18,7 @@ exports[`NumberList should be rendered with default set of props 1`] = ` onChange={[Function]} onDelete={[Function]} range={ - Range { + NumberListRange { "max": 10, "maxInclusive": true, "min": 1, @@ -45,7 +45,7 @@ exports[`NumberList should be rendered with default set of props 1`] = ` onChange={[Function]} onDelete={[Function]} range={ - Range { + NumberListRange { "max": 10, "maxInclusive": true, "min": 1, 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 23e671180e9802..777b0a94f0f3d7 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 @@ -21,7 +21,7 @@ import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Range } from '../../../../../../utils/range'; +import { NumberListRange } from './range'; export interface NumberRowProps { autoFocus: boolean; @@ -29,7 +29,7 @@ export interface NumberRowProps { isInvalid: boolean; labelledbyId: string; model: NumberRowModel; - range: Range; + range: NumberListRange; onBlur(): void; onChange({ id, value }: { id: string; value: string }): void; onDelete(index: string): void; diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts new file mode 100644 index 00000000000000..e9090e5b38ef78 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts @@ -0,0 +1,126 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { forOwn } from 'lodash'; +import { parseRange } from './range'; + +describe('Range parsing utility', () => { + test('throws an error for inputs that are not formatted properly', () => { + expect(() => { + parseRange(''); + }).toThrowError(TypeError); + + expect(function() { + parseRange('p10202'); + }).toThrowError(TypeError); + + expect(function() { + parseRange('{0,100}'); + }).toThrowError(TypeError); + + expect(function() { + parseRange('[0,100'); + }).toThrowError(TypeError); + + expect(function() { + parseRange(')0,100('); + }).toThrowError(TypeError); + }); + + const tests = { + '[ 0 , 100 ]': { + props: { + min: 0, + max: 100, + minInclusive: true, + maxInclusive: true, + }, + within: [ + [0, true], + [0.0000001, true], + [1, true], + [99.99999, true], + [100, true], + ], + }, + '(26.3 , 42]': { + props: { + min: 26.3, + max: 42, + minInclusive: false, + maxInclusive: true, + }, + within: [ + [26.2999999, false], + [26.3000001, true], + [30, true], + [41, true], + [42, true], + ], + }, + '(-50,50)': { + props: { + min: -50, + max: 50, + minInclusive: false, + maxInclusive: false, + }, + within: [ + [-50, false], + [-49.99999, true], + [0, true], + [49.99999, true], + [50, false], + ], + }, + '(Infinity, -Infinity)': { + props: { + min: -Infinity, + max: Infinity, + minInclusive: false, + maxInclusive: false, + }, + within: [ + [0, true], + [-0.0000001, true], + [-1, true], + [-10000000000, true], + [-Infinity, false], + [Infinity, false], + ], + }, + }; + + forOwn(tests, (spec, str: any) => { + // eslint-disable-next-line jest/valid-describe + describe(str, () => { + const range = parseRange(str); + + it('creation', () => { + expect(range).toEqual(spec.props); + }); + + spec.within.forEach((tup: any[]) => { + it('#within(' + tup[0] + ')', () => { + expect(range.within(tup[0])).toBe(tup[1]); + }); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts new file mode 100644 index 00000000000000..da3b7a61aea9d2 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.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. + */ + +/** + * Regexp portion that matches our number + * + * supports: + * -100 + * -100.0 + * 0 + * 0.10 + * Infinity + * -Infinity + * + * @type {String} + */ +const _RE_NUMBER = '(\\-?(?:\\d+(?:\\.\\d+)?|Infinity))'; + +/** + * Regexp for the interval notation + * + * supports: + * [num, num] + * ( num , num ] + * [Infinity,num) + * + * @type {RegExp} + */ +const RANGE_RE = new RegExp( + '^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$' +); + +export class NumberListRange { + constructor( + public minInclusive: boolean, + public min: number, + public max: number, + public maxInclusive: boolean + ) {} + + within(n: number): boolean { + if ((this.min === n && !this.minInclusive) || this.min > n) return false; + if ((this.max === n && !this.maxInclusive) || this.max < n) return false; + + return true; + } +} + +export function parseRange(input: string): NumberListRange { + const match = String(input).match(RANGE_RE); + if (!match) { + throw new TypeError('expected input to be in interval notation e.g., (100, 200]'); + } + + const args = [match[1] === '[', parseFloat(match[2]), parseFloat(match[3]), match[4] === ']']; + + if (args[1] > args[2]) { + args.reverse(); + } + + const [minInclusive, min, max, maxInclusive] = args; + + return new NumberListRange( + minInclusive as boolean, + min as number, + max as number, + maxInclusive as boolean + ); +} 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 index c6772cc1087627..89fb5738db379f 100644 --- 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 @@ -27,12 +27,12 @@ import { getNextModel, getRange, } from './utils'; -import { Range } from '../../../../../../utils/range'; +import { NumberListRange } from './range'; import { NumberRowModel } from './number_row'; describe('NumberList utils', () => { let modelList: NumberRowModel[]; - let range: Range; + let range: NumberListRange; beforeEach(() => { modelList = [ 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 563e8f0a6a9b7f..399253f27445c2 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 @@ -21,7 +21,7 @@ import { last } from 'lodash'; import { i18n } from '@kbn/i18n'; import { htmlIdGenerator } from '@elastic/eui'; -import { parseRange, Range } from '../../../../../../utils/range'; +import { parseRange, NumberListRange } from './range'; import { NumberRowModel } from './number_row'; const EMPTY_STRING = ''; @@ -34,7 +34,7 @@ function parse(value: string) { return isNaN(parsedValue) ? EMPTY_STRING : parsedValue; } -function getRange(range?: string): Range { +function getRange(range?: string): NumberListRange { try { return range ? parseRange(range) : defaultRange; } catch (e) { @@ -42,7 +42,7 @@ function getRange(range?: string): Range { } } -function validateValue(value: number | '', numberRange: Range) { +function validateValue(value: number | '', numberRange: NumberListRange) { const result: { isInvalid: boolean; error?: string } = { isInvalid: false, }; @@ -76,7 +76,7 @@ function validateOrder(list: Array) { return result; } -function getNextModel(list: NumberRowModel[], range: Range): NumberRowModel { +function getNextModel(list: NumberRowModel[], range: NumberListRange): NumberRowModel { const lastValue = last(list).value; let next = Number(lastValue) ? Number(lastValue) + 1 : 1; @@ -104,7 +104,7 @@ function getInitModelList(list: Array): NumberRowModel[] { function getUpdatedModels( numberList: Array, modelList: NumberRowModel[], - numberRange: Range, + numberRange: NumberListRange, invalidOrderModelIndex?: number ): NumberRowModel[] { if (!numberList.length) { 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 664a0b3e02a008..4f700229831a63 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx @@ -20,10 +20,9 @@ import React, { useState } from 'react'; import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from 'plugins/data'; -import { Query } from 'src/plugins/data/public'; import { AggConfig } from '../../..'; import { npStart } from '../../../../new_platform'; +import { Query, QueryStringInput } from '../../../../../../../plugins/data/public'; import { Storage } from '../../../../../../../plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../../../../plugins/kibana_react/public'; const localStorage = new Storage(window.localStorage); diff --git a/src/legacy/ui/public/vis/editors/default/default.html b/src/legacy/ui/public/vis/editors/default/default.html index 2a759815f57f29..60fcbafdb88f59 100644 --- a/src/legacy/ui/public/vis/editors/default/default.html +++ b/src/legacy/ui/public/vis/editors/default/default.html @@ -11,6 +11,7 @@
diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js index 43d2962df0a1e0..9df866d29a8a28 100644 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ b/src/legacy/ui/public/vis/editors/default/default.js @@ -33,12 +33,11 @@ import { keyCodes } from '@elastic/eui'; import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; import { DefaultEditorSize } from '../../editor_size'; -import { VisEditorTypesRegistryProvider } from '../../../registry/vis_editor_types'; import { AggGroupNames } from './agg_groups'; import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; -const defaultEditor = function ($rootScope, $compile, getAppState) { +const defaultEditor = function ($rootScope, $compile) { return class DefaultEditor { static key = 'default'; @@ -58,7 +57,7 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { } } - render({ uiState, timeRange, filters, query }) { + render({ uiState, timeRange, filters, query, appState }) { let $scope; const updateScope = () => { @@ -161,7 +160,7 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(this.savedObj, { uiState: uiState, - appState: getAppState(), + appState, timeRange: timeRange, filters: filters || [], query: query, @@ -195,6 +194,4 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { }; }; -VisEditorTypesRegistryProvider.register(defaultEditor); - export { defaultEditor }; diff --git a/src/legacy/ui/public/vis/map/convert_to_geojson.js b/src/legacy/ui/public/vis/map/convert_to_geojson.js index 77896490678ff1..14c282b58beda5 100644 --- a/src/legacy/ui/public/vis/map/convert_to_geojson.js +++ b/src/legacy/ui/public/vis/map/convert_to_geojson.js @@ -17,10 +17,9 @@ * under the License. */ -import { decodeGeoHash } from 'ui/utils/decode_geo_hash'; +import { decodeGeoHash } from './decode_geo_hash'; import { gridDimensions } from './grid_dimensions'; - export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metric }) { let features; diff --git a/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts new file mode 100644 index 00000000000000..c1ca7e4c803834 --- /dev/null +++ b/src/legacy/ui/public/vis/map/decode_geo_hash.test.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 { geohashColumns, decodeGeoHash } from './decode_geo_hash'; + +test('geohashColumns', () => { + expect(geohashColumns(1)).toBe(8); + expect(geohashColumns(2)).toBe(8 * 4); + expect(geohashColumns(3)).toBe(8 * 4 * 8); + expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4); +}); + +test('decodeGeoHash', () => { + expect(decodeGeoHash('drm3btev3e86')).toEqual({ + latitude: [41.119999922811985, 41.12000009045005, 41.12000000663102], + longitude: [-71.34000029414892, -71.3399999588728, -71.34000012651086], + }); +}); diff --git a/src/legacy/ui/public/utils/decode_geo_hash.ts b/src/legacy/ui/public/vis/map/decode_geo_hash.ts similarity index 100% rename from src/legacy/ui/public/utils/decode_geo_hash.ts rename to src/legacy/ui/public/vis/map/decode_geo_hash.ts diff --git a/src/legacy/ui/public/vis/map/kibana_map.js b/src/legacy/ui/public/vis/map/kibana_map.js index f92fc1c681d8a0..cb618444af7cee 100644 --- a/src/legacy/ui/public/vis/map/kibana_map.js +++ b/src/legacy/ui/public/vis/map/kibana_map.js @@ -22,7 +22,7 @@ import { createZoomWarningMsg } from './map_messages'; import L from 'leaflet'; import $ from 'jquery'; import _ from 'lodash'; -import { zoomToPrecision } from '../../utils/zoom_to_precision'; +import { zoomToPrecision } from './zoom_to_precision'; import { i18n } from '@kbn/i18n'; import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin'; @@ -564,6 +564,8 @@ export class KibanaMap extends EventEmitter { let baseLayer; if (settings.baseLayerType === 'wms') { + //This is user-input that is rendered with the Leaflet attribution control. Needs to be sanitized. + this._baseLayerSettings.options.attribution = _.escape(settings.options.attribution); baseLayer = this._getWMSBaseLayer(settings.options); } else if (settings.baseLayerType === 'tms') { baseLayer = this._getTMSBaseLayer((settings.options)); diff --git a/src/legacy/ui/public/vis/map/zoom_to_precision.ts b/src/legacy/ui/public/vis/map/zoom_to_precision.ts new file mode 100644 index 00000000000000..552c509590286e --- /dev/null +++ b/src/legacy/ui/public/vis/map/zoom_to_precision.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 { geohashColumns } from './decode_geo_hash'; + +const defaultMaxPrecision = 12; +const minGeoHashPixels = 16; + +const calculateZoomToPrecisionMap = (maxZoom: number): Map => { + /** + * Map Leaflet zoom levels to geohash precision levels. + * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. + */ + const zoomPrecisionMap = new Map(); + + for (let zoom = 0; zoom <= maxZoom; zoom += 1) { + if (typeof zoomPrecisionMap.get(zoom) === 'number') { + continue; + } + + const worldPixels = 256 * Math.pow(2, zoom); + + zoomPrecisionMap.set(zoom, 1); + + for (let precision = 2; precision <= defaultMaxPrecision; precision += 1) { + const columns = geohashColumns(precision); + + if (worldPixels / columns >= minGeoHashPixels) { + zoomPrecisionMap.set(zoom, precision); + } else { + break; + } + } + } + + return zoomPrecisionMap; +}; + +export function zoomToPrecision(mapZoom: number, maxPrecision: number, maxZoom: number) { + const zoomPrecisionMap = calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); + const precision = zoomPrecisionMap.get(mapZoom); + + return precision ? Math.min(precision, maxPrecision) : maxPrecision; +} diff --git a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js deleted file mode 100644 index 4ad579e1e45f9a..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js +++ /dev/null @@ -1,159 +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 'jquery'; -import _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { Vis } from '../../../../../core_plugins/visualizations/public/np_ready/public/vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('visualize_legend directive', function () { - let $rootScope; - let $compile; - let $timeout; - let $el; - let indexPattern; - let fixtures; - - beforeEach(ngMock.module('kibana', 'kibana/table_vis')); - beforeEach(ngMock.inject(function (Private, $injector) { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - $timeout = $injector.get('$timeout'); - fixtures = require('fixtures/fake_hierarchical_data'); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - // basically a parameterized beforeEach - function init(vis, esResponse) { - vis.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); - - $rootScope.vis = vis; - $rootScope.visData = esResponse; - $rootScope.uiState = require('fixtures/mock_ui_state'); - $el = $(''); - $compile($el)($rootScope); - $rootScope.$apply(); - } - - function CreateVis(params, requiresSearch) { - const vis = new Vis(indexPattern, { - type: 'line', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 } - ] - } - } - ] - }); - - vis.type.requestHandler = requiresSearch ? 'default' : 'none'; - vis.type.responseHandler = 'none'; - vis.type.requiresSearch = false; - return vis; - } - - it('calls highlight handler when highlight function is called', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - let highlight = 0; - _.set(vis, 'vislibVis.handler.highlight', () => { highlight++; }); - $rootScope.highlight({ currentTarget: null }); - expect(highlight).to.equal(1); - }); - - it('calls unhighlight handler when unhighlight function is called', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - let unhighlight = 0; - _.set(vis, 'vislibVis.handler.unHighlight', () => { unhighlight++; }); - $rootScope.unhighlight({ currentTarget: null }); - expect(unhighlight).to.equal(1); - }); - - describe('setColor function', () => { - beforeEach(() => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - }); - - it('sets the color in the UI state', () => { - $rootScope.setColor('test', '#ffffff'); - const colors = $rootScope.uiState.get('vis.colors'); - expect(colors.test).to.equal('#ffffff'); - }); - }); - - describe('toggleLegend function', () => { - let vis; - - beforeEach(() => { - const requiresSearch = false; - vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - }); - - it('sets the color in the UI state', () => { - $rootScope.open = true; - $rootScope.toggleLegend(); - $rootScope.$digest(); - $timeout.flush(); - $timeout.verifyNoPendingTasks(); - let legendOpen = $rootScope.uiState.get('vis.legendOpen'); - expect(legendOpen).to.equal(false); - - $rootScope.toggleLegend(); - $rootScope.$digest(); - $timeout.flush(); - $timeout.verifyNoPendingTasks(); - legendOpen = $rootScope.uiState.get('vis.legendOpen'); - expect(legendOpen).to.equal(true); - }); - }); - - it('does not update scope.data if visData is null', () => { - $rootScope.visData = null; - $rootScope.$digest(); - expect($rootScope.data).to.not.equal(null); - }); - - it('works without handler set', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - vis.vislibVis = {}; - init(vis, fixtures.oneRangeBucket); - expect(() => { - $rootScope.highlight({ currentTarget: null }); - $rootScope.unhighlight({ currentTarget: null }); - }).to.not.throwError(); - }); -}); diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss index 8de88959cfb59b..4d7c0e2bdcadb4 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss @@ -11,9 +11,11 @@ $visLegendLineHeight: $euiSize; position: absolute; bottom: 0; left: 0; + display: flex; + padding: $euiSizeXS; background-color: $euiColorEmptyShade; transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, - background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; + background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; &:focus { box-shadow: none; @@ -22,13 +24,11 @@ $visLegendLineHeight: $euiSize; } .visLegend__toggle--isOpen { - background-color: transparentize($euiColorDarkestShade, .9); + background-color: transparentize($euiColorDarkestShade, 0.9); opacity: 1; } - .visLegend { - @include euiFontSizeXS; display: flex; min-height: 0; height: 100%; @@ -46,27 +46,30 @@ $visLegendLineHeight: $euiSize; } } -/** - * 1. Position the .visLegend__valueDetails absolutely against the legend item - * 2. Make sure the .visLegend__valueDetails is visible outside the list bounds - * 3. Make sure the currently selected item is top most in z level - */ .visLegend__list { @include euiScrollBar; display: flex; - line-height: $visLegendLineHeight; width: $visLegendWidth; // Must be a hard-coded width for the chart to get its correct dimensions flex: 1 1 auto; flex-direction: column; overflow-x: hidden; overflow-y: auto; + .visLegend__button { + font-size: $euiFontSizeXS; + text-align: left; + overflow: hidden; // Ensures scrollbars don't appear because EuiButton__text has a high line-height + + .visLegend__valueTitle { + vertical-align: middle; + } + } + .visLib--legend-top &, .visLib--legend-bottom & { width: auto; flex-direction: row; flex-wrap: wrap; - overflow: visible; /* 2 */ .visLegend__value { flex-grow: 0; @@ -79,74 +82,19 @@ $visLegendLineHeight: $euiSize; } } -.visLegend__value { - cursor: pointer; - padding: $euiSizeXS; - display: flex; - flex-shrink: 0; - position: relative; /* 1 */ - - > * { - width: 100%; - } - - &.disabled { - opacity: 0.5; - } +.visLegend__valueColorPicker { + width: ($euiSizeL * 8); // 8 columns } -.visLegend__valueTitle { - @include euiTextTruncate; // ALWAYS truncate - color: $visTextColor; +.visLegend__valueColorPickerDot { + cursor: pointer; &:hover { - text-decoration: underline; - } -} - -.visLegend__valueTitle--full ~ .visLegend__valueDetails { - z-index: 2; /* 3 */ -} - -.visLegend__valueDetails { - background-color: $euiColorEmptyShade; - - .visLib--legend-left &, - .visLib--legend-right & { - margin-top: $euiSizeXS; - border-bottom: $euiBorderThin; - } - - .visLib--legend-top &, - .visLib--legend-bottom & { - @include euiBottomShadowMedium; - position: absolute; /* 1 */ - border-radius: $euiBorderRadius; + transform: scale(1.4); } - .visLib--legend-bottom & { - bottom: $visLegendLineHeight + 2 * $euiSizeXS; - } - - .visLib--legend-top & { - margin-top: $euiSizeXS; - } -} - -.visLegend__valueColorPicker { - width: $visColorPickerWidth; - margin: auto; - - .visLegend__valueColorPickerDot { - $colorPickerDotsPerRow: 8; - $colorPickerDotMargin: $euiSizeXS / 2; - $colorPickerDotWidth: $visColorPickerWidth / $colorPickerDotsPerRow - 2 * $colorPickerDotMargin; - - margin: $colorPickerDotMargin; - width: $colorPickerDotWidth; - - &:hover { - transform: scale(1.4); - } + &-isSelected { + border: $euiSizeXS solid; + border-radius: 100%; } } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html deleted file mode 100644 index 70d2a796658f2f..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html +++ /dev/null @@ -1,99 +0,0 @@ -
- -
    - -
  • - -
    -
    - - {{legendData.label}} -
    - -
    -
    - - - -
    - -
    - - - - -
    - -
    -
    - -
  • -
-
diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js deleted file mode 100644 index 3d054b8f8a2fbd..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js +++ /dev/null @@ -1,186 +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 html from './vislib_vis_legend.html'; -import { Data } from '../../vislib/lib/data'; -import { uiModules } from '../../modules'; -import { createFiltersFromEvent } from '../../../../core_plugins/visualizations/public'; -import { htmlIdGenerator, keyCodes } from '@elastic/eui'; -import { getTableAggs } from '../../visualize/loader/pipeline_helpers/utilities'; - -export const CUSTOM_LEGEND_VIS_TYPES = ['heatmap', 'gauge']; - -uiModules.get('kibana') - .directive('vislibLegend', function ($timeout) { - - return { - restrict: 'E', - template: html, - link: function ($scope) { - $scope.legendId = htmlIdGenerator()('legend'); - $scope.open = $scope.uiState.get('vis.legendOpen', true); - - $scope.$watch('visData', function (data) { - if (!data) return; - $scope.data = data; - }); - - $scope.$watch('refreshLegend', () => { - refresh(); - }); - - $scope.highlight = function (event) { - const el = event.currentTarget; - const handler = $scope.vis.vislibVis.handler; - - //there is no guarantee that a Chart will set the highlight-function on its handler - if (!handler || typeof handler.highlight !== 'function') { - return; - } - handler.highlight.call(el, handler.el); - }; - - $scope.unhighlight = function (event) { - const el = event.currentTarget; - const handler = $scope.vis.vislibVis.handler; - //there is no guarantee that a Chart will set the unhighlight-function on its handler - if (!handler || typeof handler.unHighlight !== 'function') { - return; - } - handler.unHighlight.call(el, handler.el); - }; - - $scope.setColor = function (label, color) { - const colors = $scope.uiState.get('vis.colors') || {}; - if (colors[label] === color) delete colors[label]; - else colors[label] = color; - $scope.uiState.setSilent('vis.colors', null); - $scope.uiState.set('vis.colors', colors); - $scope.uiState.emit('colorChanged'); - refresh(); - }; - - $scope.toggleLegend = function () { - const bwcAddLegend = $scope.vis.params.addLegend; - const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; - $scope.open = !$scope.uiState.get('vis.legendOpen', bwcLegendStateDefault); - // open should be applied on template before we update uiState - $timeout(() => { - $scope.uiState.set('vis.legendOpen', $scope.open); - }); - }; - - $scope.filter = function (legendData, negate) { - $scope.vis.API.events.filter({ data: legendData.values, negate: negate }); - }; - - $scope.canFilter = function (legendData) { - if (CUSTOM_LEGEND_VIS_TYPES.includes($scope.vis.vislibVis.visConfigArgs.type)) { - return false; - } - const filters = createFiltersFromEvent({ aggConfigs: $scope.tableAggs, data: legendData.values }); - return filters.length; - }; - - /** - * Keydown listener for a legend entry. - * This will close the details panel of this legend entry when pressing Escape. - */ - $scope.onLegendEntryKeydown = function (event) { - if (event.keyCode === keyCodes.ESCAPE) { - event.preventDefault(); - event.stopPropagation(); - $scope.shownDetails = undefined; - } - }; - - $scope.toggleDetails = function (label) { - $scope.shownDetails = $scope.shownDetails === label ? undefined : label; - }; - - $scope.areDetailsVisible = function (label) { - return $scope.shownDetails === label; - }; - - $scope.colors = [ - '#3F6833', '#967302', '#2F575E', '#99440A', '#58140C', '#052B51', '#511749', '#3F2B5B', //6 - '#508642', '#CCA300', '#447EBC', '#C15C17', '#890F02', '#0A437C', '#6D1F62', '#584477', //2 - '#629E51', '#E5AC0E', '#64B0C8', '#E0752D', '#BF1B00', '#0A50A1', '#962D82', '#614D93', //4 - '#7EB26D', '#EAB839', '#6ED0E0', '#EF843C', '#E24D42', '#1F78C1', '#BA43A9', '#705DA0', // Normal - '#9AC48A', '#F2C96D', '#65C5DB', '#F9934E', '#EA6460', '#5195CE', '#D683CE', '#806EB7', //5 - '#B7DBAB', '#F4D598', '#70DBED', '#F9BA8F', '#F29191', '#82B5D8', '#E5A8E2', '#AEA2E0', //3 - '#E0F9D7', '#FCEACA', '#CFFAFF', '#F9E2D2', '#FCE2DE', '#BADFF4', '#F9D9F9', '#DEDAF7' //7 - ]; - - function refresh() { - const vislibVis = $scope.vis.vislibVis; - if (!vislibVis || !vislibVis.visConfig) { - $scope.labels = [{ label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { defaultMessage: 'loading…' }) }]; - return; - } // make sure vislib is defined at this point - - if ($scope.uiState.get('vis.legendOpen') == null && $scope.vis.params.addLegend != null) { - $scope.open = $scope.vis.params.addLegend; - } - - if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { - const labels = vislibVis.getLegendLabels(); - if (labels) { - $scope.labels = _.map(labels, label => { - return { label: label }; - }); - } - } else { - $scope.labels = getLabels($scope.data, vislibVis.visConfigArgs.type); - } - - if (vislibVis.visConfig) { - $scope.getColor = vislibVis.visConfig.data.getColorFunc(); - } - - $scope.tableAggs = getTableAggs($scope.vis); - } - - // Most of these functions were moved directly from the old Legend class. Not a fan of this. - function getLabels(data, type) { - if (!data) return []; - data = data.columns || data.rows || [data]; - if (type === 'pie') return Data.prototype.pieNames(data); - return getSeriesLabels(data); - } - - function getSeriesLabels(data) { - const values = data.map(function (chart) { - return chart.series; - }) - .reduce(function (a, b) { - return a.concat(b); - }, []); - return _.compact(_.uniq(values, 'label')).map(label => { - return { - ...label, - values: [label.values[0].seriesRaw], - }; - }); - } - } - }; - }); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap new file mode 100644 index 00000000000000..f2c9f4e1b53ec3 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VisLegend Component Legend closed should match the snapshot 1`] = `"
"`; + +exports[`VisLegend Component Legend open should match the snapshot 1`] = `"
"`; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts new file mode 100644 index 00000000000000..ebf132f0ab697f --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/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 { VisLegend } from './vislib_vis_legend'; +export { CUSTOM_LEGEND_VIS_TYPES } from './models'; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts new file mode 100644 index 00000000000000..1c8d5baf011a34 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 LegendItem { + label: string; + values: any[]; +} + +export const CUSTOM_LEGEND_VIS_TYPES = ['heatmap', 'gauge']; + +export const legendColors: string[] = [ + '#3F6833', + '#967302', + '#2F575E', + '#99440A', + '#58140C', + '#052B51', + '#511749', + '#3F2B5B', // 6 + '#508642', + '#CCA300', + '#447EBC', + '#C15C17', + '#890F02', + '#0A437C', + '#6D1F62', + '#584477', // 2 + '#629E51', + '#E5AC0E', + '#64B0C8', + '#E0752D', + '#BF1B00', + '#0A50A1', + '#962D82', + '#614D93', // 4 + '#7EB26D', + '#EAB839', + '#6ED0E0', + '#EF843C', + '#E24D42', + '#1F78C1', + '#BA43A9', + '#705DA0', // Normal + '#9AC48A', + '#F2C96D', + '#65C5DB', + '#F9934E', + '#EA6460', + '#5195CE', + '#D683CE', + '#806EB7', // 5 + '#B7DBAB', + '#F4D598', + '#70DBED', + '#F9BA8F', + '#F29191', + '#82B5D8', + '#E5A8E2', + '#AEA2E0', // 3 + '#E0F9D7', + '#FCEACA', + '#CFFAFF', + '#F9E2D2', + '#FCE2DE', + '#BADFF4', + '#F9D9F9', + '#DEDAF7', // 7 +]; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx new file mode 100644 index 00000000000000..839dc0024bbea5 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx @@ -0,0 +1,276 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiButtonGroup } from '@elastic/eui'; + +import { VisLegend, VisLegendProps } from '../vislib_vis_legend/vislib_vis_legend'; +import { legendColors } from './models'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + htmlIdGenerator: jest.fn().mockReturnValue(() => 'legendId'), +})); + +jest.mock('../../../visualize/loader/pipeline_helpers/utilities', () => ({ + getTableAggs: jest.fn(), +})); +jest.mock('../../../../../core_plugins/visualizations/public', () => ({ + createFiltersFromEvent: jest.fn().mockReturnValue(['yes']), +})); + +const vis = { + params: { + addLegend: true, + }, + API: { + events: { + filter: jest.fn(), + }, + }, +}; +const vislibVis = { + handler: { + highlight: jest.fn(), + unHighlight: jest.fn(), + }, + getLegendLabels: jest.fn(), + visConfigArgs: { + type: 'area', + }, + visConfig: { + data: { + getColorFunc: jest.fn().mockReturnValue(() => 'red'), + }, + }, +}; + +const visData = { + series: [ + { + label: 'A', + values: [ + { + seriesRaw: 'valuesA', + }, + ], + }, + { + label: 'B', + values: [ + { + seriesRaw: 'valuesB', + }, + ], + }, + ], +}; + +const mockState = new Map(); +const uiState = { + get: jest + .fn() + .mockImplementation((key, fallback) => (mockState.has(key) ? mockState.get(key) : fallback)), + set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)), + emit: jest.fn(), + setSilent: jest.fn(), +}; + +const getWrapper = (props?: Partial) => + mount( + + + + ); + +const getLegendItems = (wrapper: ReactWrapper) => wrapper.find('.visLegend__button'); + +describe('VisLegend Component', () => { + let wrapper: ReactWrapper; + + afterEach(() => { + mockState.clear(); + jest.clearAllMocks(); + }); + + describe('Legend open', () => { + beforeEach(() => { + mockState.set('vis.legendOpen', true); + wrapper = getWrapper(); + }); + + it('should match the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('Legend closed', () => { + beforeEach(() => { + mockState.set('vis.legendOpen', false); + wrapper = getWrapper(); + }); + + it('should match the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('Highlighting', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should call highlight handler when legend item is focused', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('focus'); + + expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); + }); + + it('should call highlight handler when legend item is hovered', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('mouseEnter'); + + expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); + }); + + it('should call unHighlight handler when legend item is blurred', () => { + let first = getLegendItems(wrapper).first(); + first.simulate('focus'); + first = getLegendItems(wrapper).first(); + first.simulate('blur'); + + expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); + }); + + it('should call unHighlight handler when legend item is unhovered', () => { + const first = getLegendItems(wrapper).first(); + + first.simulate('mouseEnter'); + first.simulate('mouseLeave'); + + expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); + }); + + it('should work with no handlers set', () => { + const newVis = { + ...vis, + vislibVis: { + ...vislibVis, + handler: null, + }, + }; + + expect(() => { + wrapper = getWrapper({ vis: newVis }); + const first = getLegendItems(wrapper).first(); + first.simulate('focus'); + first.simulate('blur'); + }).not.toThrow(); + }); + }); + + describe('Filtering', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should filter out when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + const filterGroup = wrapper.find(EuiButtonGroup).first(); + filterGroup.getElement().props.onChange('filterIn'); + + expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: false }); + expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + }); + + it('should filter in when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + const filterGroup = wrapper.find(EuiButtonGroup).first(); + filterGroup.getElement().props.onChange('filterOut'); + + expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: true }); + expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + }); + }); + + describe('Toggles details', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should show details when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + + expect(wrapper.exists('.visLegend__valueDetails')).toBe(true); + }); + }); + + describe('setColor', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('sets the color in the UI state', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + + const popover = wrapper.find('.visLegend__valueDetails').first(); + const firstColor = popover.find('.visLegend__valueColorPickerDot').first(); + firstColor.simulate('click'); + + const colors = mockState.get('vis.colors'); + + expect(colors.A).toBe(legendColors[0]); + }); + }); + + describe('toggleLegend function', () => { + it('click should show legend once toggled from hidden', () => { + mockState.set('vis.legendOpen', false); + wrapper = getWrapper(); + const toggleButton = wrapper.find('.visLegend__toggle').first(); + toggleButton.simulate('click'); + + expect(wrapper.exists('.visLegend__list')).toBe(true); + }); + + it('click should hide legend once toggled from shown', () => { + mockState.set('vis.legendOpen', true); + wrapper = getWrapper(); + const toggleButton = wrapper.find('.visLegend__toggle').first(); + toggleButton.simulate('click'); + + expect(wrapper.exists('.visLegend__list')).toBe(false); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx new file mode 100644 index 00000000000000..f0100e369f0507 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx @@ -0,0 +1,264 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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, { BaseSyntheticEvent, KeyboardEvent, PureComponent } from 'react'; +import classNames from 'classnames'; +import { compact, uniq, map } from 'lodash'; + +import { i18n } from '@kbn/i18n'; +import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; + +// @ts-ignore +import { Data } from '../../../vislib/lib/data'; +// @ts-ignore +import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; +import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; +import { VisLegendItem } from './vislib_vis_legend_item'; +import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; + +export interface VisLegendProps { + vis: any; + vislibVis: any; + visData: any; + uiState: any; + position: 'top' | 'bottom' | 'left' | 'right'; +} + +export interface VisLegendState { + open: boolean; + labels: any[]; + tableAggs: any[]; + selectedLabel: string | null; +} + +export class VisLegend extends PureComponent { + legendId = htmlIdGenerator()('legend'); + getColor: (label: string) => string = () => ''; + + constructor(props: VisLegendProps) { + super(props); + const open = props.uiState.get('vis.legendOpen', true); + + this.state = { + open, + labels: [], + tableAggs: [], + selectedLabel: null, + }; + } + + componentDidMount() { + this.refresh(); + } + + toggleLegend = () => { + const bwcAddLegend = this.props.vis.params.addLegend; + const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; + const newOpen = !this.props.uiState.get('vis.legendOpen', bwcLegendStateDefault); + this.setState({ open: newOpen }); + // open should be applied on template before we update uiState + setTimeout(() => { + this.props.uiState.set('vis.legendOpen', newOpen); + }); + }; + + setColor = (label: string, color: string) => (event: BaseSyntheticEvent) => { + if ((event as KeyboardEvent).keyCode && (event as KeyboardEvent).keyCode !== keyCodes.ENTER) { + return; + } + + const colors = this.props.uiState.get('vis.colors') || {}; + if (colors[label] === color) delete colors[label]; + else colors[label] = color; + this.props.uiState.setSilent('vis.colors', null); + this.props.uiState.set('vis.colors', colors); + this.props.uiState.emit('colorChanged'); + this.refresh(); + }; + + filter = ({ values: data }: LegendItem, negate: boolean) => { + this.props.vis.API.events.filter({ data, negate }); + }; + + canFilter = (item: LegendItem): boolean => { + if (CUSTOM_LEGEND_VIS_TYPES.includes(this.props.vislibVis.visConfigArgs.type)) { + return false; + } + const filters = createFiltersFromEvent({ aggConfigs: this.state.tableAggs, data: item.values }); + return Boolean(filters.length); + }; + + toggleDetails = (label: string | null) => (event?: BaseSyntheticEvent) => { + if ( + event && + (event as KeyboardEvent).keyCode && + (event as KeyboardEvent).keyCode !== keyCodes.ENTER + ) { + return; + } + this.setState({ selectedLabel: this.state.selectedLabel === label ? null : label }); + }; + + getSeriesLabels = (data: any[]) => { + const values = data.map(chart => chart.series).reduce((a, b) => a.concat(b), []); + + return compact(uniq(values, 'label')).map((label: any) => ({ + ...label, + values: [label.values[0].seriesRaw], + })); + }; + + // Most of these functions were moved directly from the old Legend class. Not a fan of this. + getLabels = (data: any, type: string) => { + if (!data) return []; + data = data.columns || data.rows || [data]; + + if (type === 'pie') return Data.prototype.pieNames(data); + + return this.getSeriesLabels(data); + }; + + refresh = () => { + const vislibVis = this.props.vislibVis; + if (!vislibVis || !vislibVis.visConfig) { + this.setState({ + labels: [ + { + label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { + defaultMessage: 'loading…', + }), + }, + ], + }); + return; + } // make sure vislib is defined at this point + + if ( + this.props.uiState.get('vis.legendOpen') == null && + this.props.vis.params.addLegend != null + ) { + this.setState({ open: this.props.vis.params.addLegend }); + } + + if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { + const legendLabels = this.props.vislibVis.getLegendLabels(); + if (legendLabels) { + this.setState({ + labels: map(legendLabels, label => { + return { label }; + }), + }); + } + } else { + this.setState({ labels: this.getLabels(this.props.visData, vislibVis.visConfigArgs.type) }); + } + + if (vislibVis.visConfig) { + this.getColor = this.props.vislibVis.visConfig.data.getColorFunc(); + } + + this.setState({ tableAggs: getTableAggs(this.props.vis) }); + }; + + highlight = (event: BaseSyntheticEvent) => { + const el = event.currentTarget; + const handler = this.props.vislibVis && this.props.vislibVis.handler; + + // there is no guarantee that a Chart will set the highlight-function on its handler + if (!handler || typeof handler.highlight !== 'function') { + return; + } + handler.highlight.call(el, handler.el); + }; + + unhighlight = (event: BaseSyntheticEvent) => { + const el = event.currentTarget; + const handler = this.props.vislibVis && this.props.vislibVis.handler; + + // there is no guarantee that a Chart will set the unhighlight-function on its handler + if (!handler || typeof handler.unHighlight !== 'function') { + return; + } + handler.unHighlight.call(el, handler.el); + }; + + getAnchorPosition = () => { + const { position } = this.props; + + switch (position) { + case 'bottom': + return 'upCenter'; + case 'left': + return 'rightUp'; + case 'right': + return 'leftUp'; + default: + return 'downCenter'; + } + }; + + renderLegend = (anchorPosition: EuiPopoverProps['anchorPosition']) => ( +
    + {this.state.labels.map(item => ( + + ))} +
+ ); + + render() { + const { open } = this.state; + const anchorPosition = this.getAnchorPosition(); + + return ( +
+ + {open && this.renderLegend(anchorPosition)} +
+ ); + } +} diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx new file mode 100644 index 00000000000000..7376fabfe738be --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx @@ -0,0 +1,203 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo, BaseSyntheticEvent, KeyboardEvent } from 'react'; +import classNames from 'classnames'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiPopover, + keyCodes, + EuiIcon, + EuiSpacer, + EuiButtonEmpty, + EuiPopoverProps, + EuiButtonGroup, + EuiButtonGroupOption, +} from '@elastic/eui'; + +import { legendColors, LegendItem } from './models'; + +interface Props { + item: LegendItem; + legendId: string; + selected: boolean; + canFilter: boolean; + anchorPosition: EuiPopoverProps['anchorPosition']; + onFilter: (item: LegendItem, negate: boolean) => void; + onSelect: (label: string | null) => (event?: BaseSyntheticEvent) => void; + onHighlight: (event: BaseSyntheticEvent) => void; + onUnhighlight: (event: BaseSyntheticEvent) => void; + setColor: (label: string, color: string) => (event: BaseSyntheticEvent) => void; + getColor: (label: string) => string; +} + +const VisLegendItemComponent = ({ + item, + legendId, + selected, + canFilter, + anchorPosition, + onFilter, + onSelect, + onHighlight, + onUnhighlight, + setColor, + getColor, +}: Props) => { + /** + * Keydown listener for a legend entry. + * This will close the details panel of this legend entry when pressing Escape. + */ + const onLegendEntryKeydown = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + onSelect(null)(); + } + }; + + const filterOptions: EuiButtonGroupOption[] = [ + { + id: 'filterIn', + label: i18n.translate('common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel', { + defaultMessage: 'Filter for value {legendDataLabel}', + values: { legendDataLabel: item.label }, + }), + iconType: 'plusInCircle', + 'data-test-subj': `legend-${item.label}-filterIn`, + }, + { + id: 'filterOut', + label: i18n.translate('common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel', { + defaultMessage: 'Filter out value {legendDataLabel}', + values: { legendDataLabel: item.label }, + }), + iconType: 'minusInCircle', + 'data-test-subj': `legend-${item.label}-filterOut`, + }, + ]; + + const handleFilterChange = (id: string) => { + onFilter(item, id !== 'filterIn'); + }; + + const renderFilterBar = () => ( + <> + + + + ); + + const button = ( + + + {item.label} + + ); + + const renderDetails = () => ( + +
+ {canFilter && renderFilterBar()} + +
+ + + + {legendColors.map(color => ( + + ))} +
+
+
+ ); + + return ( +
  • + {renderDetails()} +
  • + ); +}; + +export const VisLegendItem = memo(VisLegendItemComponent); diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js index 2e6436b370fbe8..7067cfac1b55dc 100644 --- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js +++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js @@ -26,8 +26,9 @@ import del from 'del'; import { makeRe } from 'minimatch'; import jsonStableStringify from 'json-stable-stringify'; -import { IS_KIBANA_DISTRIBUTABLE, fromRoot } from '../../utils'; - +import { IS_KIBANA_DISTRIBUTABLE } from '../../utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { fromRoot } from '../../../core/server/utils'; import { UiBundle } from './ui_bundle'; import { appEntryTemplate } from './app_entry_template'; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 5c1669b716ecaf..80bee411757715 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -50,13 +50,6 @@ export const UI_EXPORT_DEFAULTS = { fieldFormatEditors: [ 'ui/field_editor/components/field_format_editor/register' ], - visEditorTypes: [ - 'ui/vis/editors/default/default', - ], - embeddableFactories: [ - 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory', - 'plugins/kibana/discover/embeddable/search_embeddable_factory', - ], search: [ 'ui/courier/search_strategy/default_search_strategy', ], diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 763167c6b5ccf4..47e1e9e17c5c96 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -26,7 +26,8 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { AppBootstrap } from './bootstrap'; import { mergeVariables } from './lib'; -import { fromRoot } from '../../utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { fromRoot } from '../../../core/server/utils'; import { createCSPRuleString } from '../../server/csp'; export function uiRenderMixin(kbnServer, server, config) { @@ -279,8 +280,6 @@ export function uiRenderMixin(kbnServer, server, config) { uiPlugins, legacyMetadata, - - capabilities: await request.getCapabilities(), }, }); diff --git a/src/legacy/utils/artifact_type.ts b/src/legacy/utils/artifact_type.ts index c5d8b78eb49551..69f728e9e2220b 100644 --- a/src/legacy/utils/artifact_type.ts +++ b/src/legacy/utils/artifact_type.ts @@ -17,7 +17,7 @@ * under the License. */ -import { pkg } from './package_json'; - +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { pkg } from '../../core/server/utils'; export const IS_KIBANA_DISTRIBUTABLE = pkg.build && pkg.build.distributable === true; export const IS_KIBANA_RELEASE = pkg.build && pkg.build.release === true; diff --git a/src/legacy/utils/deep_clone_with_buffers.test.ts b/src/legacy/utils/deep_clone_with_buffers.test.ts index 8fdf1ae7bfd98e..7a0906a715c2e1 100644 --- a/src/legacy/utils/deep_clone_with_buffers.test.ts +++ b/src/legacy/utils/deep_clone_with_buffers.test.ts @@ -52,7 +52,7 @@ describe('deepCloneWithBuffers()', () => { }); it('copies buffers but keeps them buffers', () => { - const input = new Buffer('i am a teapot', 'utf8'); + const input = Buffer.from('i am a teapot', 'utf8'); const output = deepCloneWithBuffers(input); expect(Buffer.isBuffer(input)).toBe(true); @@ -65,7 +65,7 @@ describe('deepCloneWithBuffers()', () => { const input = { a: { b: { - c: new Buffer('i am a teapot', 'utf8'), + c: Buffer.from('i am a teapot', 'utf8'), }, }, }; diff --git a/src/legacy/utils/deep_clone_with_buffers.ts b/src/legacy/utils/deep_clone_with_buffers.ts index 6938b9371435e7..2e9120eb32b7c8 100644 --- a/src/legacy/utils/deep_clone_with_buffers.ts +++ b/src/legacy/utils/deep_clone_with_buffers.ts @@ -24,7 +24,7 @@ import { cloneDeep } from 'lodash'; // type of the customizer function doesn't expect that. function cloneBuffersCustomizer(val: unknown): any { if (Buffer.isBuffer(val)) { - return new Buffer(val); + return Buffer.from(val); } } diff --git a/src/legacy/utils/index.d.ts b/src/legacy/utils/index.d.ts index a7317f59637ed0..8718ffc113e104 100644 --- a/src/legacy/utils/index.d.ts +++ b/src/legacy/utils/index.d.ts @@ -20,3 +20,7 @@ export function parseCommaSeparatedList(input: string | string[]): string[]; export function formatListAsProse(list: string[], options?: { inclusive?: boolean }): string; + +export function getFlattenedObject(rootValue: Record): { [key: string]: any }; + +export function unset(object: object, rawPath: string): void; diff --git a/src/legacy/utils/index.js b/src/legacy/utils/index.js index a34c691f9578a0..fca3ea56a99483 100644 --- a/src/legacy/utils/index.js +++ b/src/legacy/utils/index.js @@ -20,8 +20,6 @@ export { BinderBase } from './binder'; export { BinderFor } from './binder_for'; export { deepCloneWithBuffers } from './deep_clone_with_buffers'; -export { fromRoot } from './from_root'; -export { pkg } from './package_json'; export { unset } from './unset'; export { encodeQueryComponent } from './encode_query_component'; export { getFlattenedObject } from './get_flattened_object'; diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js index 2eaf4c1d6e8828..3b98592fdbbed1 100644 --- a/src/optimize/base_optimizer.js +++ b/src/optimize/base_optimizer.js @@ -31,8 +31,8 @@ import WrapperPlugin from 'wrapper-webpack-plugin'; import { defaults } from 'lodash'; -import { IS_KIBANA_DISTRIBUTABLE, fromRoot } from '../legacy/utils'; - +import { IS_KIBANA_DISTRIBUTABLE } from '../legacy/utils'; +import { fromRoot } from '../core/server/utils'; import { PUBLIC_PATH_PLACEHOLDER } from './public_path_placeholder'; const POSTCSS_CONFIG_PATH = require.resolve('./postcss.config'); diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index 3f3bb3e4e196c7..e1124d82391f6e 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -19,7 +19,7 @@ import { configModel } from './dll_config_model'; import { notInNodeModulesOrWebpackShims, notInNodeModules, inDllPluginPublic } from './dll_allowed_modules'; -import { fromRoot } from '../../legacy/utils'; +import { fromRoot } from '../../core/server/utils'; import { PUBLIC_PATH_PLACEHOLDER } from '../public_path_placeholder'; import fs from 'fs'; import webpack from 'webpack'; @@ -29,7 +29,7 @@ import del from 'del'; const readFileAsync = promisify(fs.readFile); const mkdirAsync = promisify(fs.mkdir); -const existsAsync = promisify(fs.exists); +const accessAsync = promisify(fs.access); const writeFileAsync = promisify(fs.writeFile); export class DllCompiler { @@ -127,13 +127,14 @@ export class DllCompiler { } async ensurePathExists(filePath) { - const exists = await existsAsync(filePath); - - if (!exists) { + try { + await accessAsync(filePath); + } catch (e) { await mkdirAsync(path.dirname(filePath), { recursive: true }); + return false; } - return exists; + return true; } async ensureOutputPathExists() { diff --git a/src/optimize/dynamic_dll_plugin/dll_config_model.js b/src/optimize/dynamic_dll_plugin/dll_config_model.js index cb1f9be9b16c95..b914eab5b17749 100644 --- a/src/optimize/dynamic_dll_plugin/dll_config_model.js +++ b/src/optimize/dynamic_dll_plugin/dll_config_model.js @@ -17,7 +17,8 @@ * under the License. */ -import { fromRoot, IS_KIBANA_DISTRIBUTABLE } from '../../legacy/utils'; +import { IS_KIBANA_DISTRIBUTABLE } from '../../legacy/utils'; +import { fromRoot } from '../../core/server/utils'; import webpack from 'webpack'; import webpackMerge from 'webpack-merge'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; diff --git a/src/optimize/index.js b/src/optimize/index.js index 0960f9ecb10b6d..27adc241878892 100644 --- a/src/optimize/index.js +++ b/src/optimize/index.js @@ -20,9 +20,7 @@ import FsOptimizer from './fs_optimizer'; import { createBundlesRoute } from './bundles_route'; import { DllCompiler } from './dynamic_dll_plugin'; -import { fromRoot } from '../legacy/utils'; - -export default async (kbnServer, server, config) => { +import { fromRoot } from '../core/server/utils'; export default async (kbnServer, server, config) => { if (!config.get('optimize.enabled')) return; // the watch optimizer sets up two threads, one is the server listening diff --git a/src/optimize/watch/watch_optimizer.js b/src/optimize/watch/watch_optimizer.js index 159ba68a6aaba8..d3b19ccdaecd97 100644 --- a/src/optimize/watch/watch_optimizer.js +++ b/src/optimize/watch/watch_optimizer.js @@ -20,7 +20,7 @@ import BaseOptimizer from '../base_optimizer'; import { createBundlesRoute } from '../bundles_route'; import { DllCompiler } from '../dynamic_dll_plugin'; -import { fromRoot } from '../../legacy/utils'; +import { fromRoot } from '../../core/server/utils'; import * as Rx from 'rxjs'; import { mergeMap, take } from 'rxjs/operators'; 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 index de29e1dec85a86..4438a6c9971261 100644 --- 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 @@ -16,7 +16,6 @@ * 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'; @@ -29,6 +28,8 @@ import { ContactCardEmbeddableOutput, } from '../embeddable_plugin_test_samples'; import { DashboardOptions } from '../embeddable/dashboard_container_factory'; +import { coreMock } from '../../../../core/public/mocks'; +import { CoreStart } from 'kibana/public'; const embeddableFactories = new Map(); embeddableFactories.set( @@ -39,8 +40,9 @@ const getEmbeddableFactories = () => embeddableFactories.values(); let container: DashboardContainer; let embeddable: ContactCardEmbeddable; - +let coreStart: CoreStart; beforeEach(async () => { + coreStart = coreMock.createStart(); const options: DashboardOptions = { ExitFullScreenButton: () => null, SavedObjectFinder: () => null, @@ -50,7 +52,7 @@ beforeEach(async () => { } as any, inspector: {} as any, notifications: {} as any, - overlays: {} as any, + overlays: coreStart.overlays, savedObjectMetaData: {} as any, uiActions: {} as any, }; @@ -80,11 +82,10 @@ beforeEach(async () => { }); test('Executes the replace panel action', async () => { - let core: any; let SavedObjectFinder: any; let notifications: any; const action = new ReplacePanelAction( - core, + coreStart, SavedObjectFinder, notifications, getEmbeddableFactories @@ -93,11 +94,10 @@ test('Executes the replace panel action', async () => { }); 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, + coreStart, SavedObjectFinder, notifications, getEmbeddableFactories @@ -113,11 +113,10 @@ test('Is not compatible when embeddable is not in a dashboard container', async }); 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, + coreStart, SavedObjectFinder, notifications, getEmbeddableFactories @@ -129,11 +128,10 @@ test('Execute throws an error when called with an embeddable not in a parent', a }); test('Returns title', async () => { - let core: any; let SavedObjectFinder: any; let notifications: any; const action = new ReplacePanelAction( - core, + coreStart, SavedObjectFinder, notifications, getEmbeddableFactories @@ -142,11 +140,10 @@ test('Returns title', async () => { }); test('Returns an icon', async () => { - let core: any; let SavedObjectFinder: any; let notifications: any; const action = new ReplacePanelAction( - core, + coreStart, SavedObjectFinder, notifications, getEmbeddableFactories diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx index 684aa93779bc13..021a1a9d1e64ac 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx @@ -90,6 +90,8 @@ export type DashboardReactContext = KibanaReactContext { public readonly type = DASHBOARD_CONTAINER_TYPE; + public renderEmpty?: undefined | (() => React.ReactNode); + constructor( initialInput: DashboardContainerInput, private readonly options: DashboardContainerOptions, @@ -124,7 +126,7 @@ export class DashboardContainer extends Container - + , dom diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss index 24b813ec589642..0bd356522c7fa1 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss @@ -34,7 +34,7 @@ .dshLayout-isMaximizedPanel { height: 100% !important; /* 1. */ width: 100%; - position: absolute; + position: absolute !important; } /** diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx index e4338dc89153d3..c1a3d88979f490 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx @@ -21,7 +21,7 @@ import sizeMe from 'react-sizeme'; import React from 'react'; -import { nextTick, mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { skip } from 'rxjs/operators'; import { EmbeddableFactory, GetEmbeddableFactory } from '../../embeddable_plugin'; import { DashboardGrid, DashboardGridProps } from './dashboard_grid'; @@ -65,10 +65,14 @@ function prepare(props?: Partial) { } as any, notifications: {} as any, overlays: {} as any, - inspector: {} as any, + inspector: { + isAvailable: jest.fn(), + } as any, SavedObjectFinder: () => null, ExitFullScreenButton: () => null, - uiActions: {} as any, + uiActions: { + getTriggerCompatibleActions: (() => []) as any, + } as any, }; dashboardContainer = new DashboardContainer(initialInput, options); const defaultTestProps: DashboardGridProps = { @@ -100,12 +104,11 @@ test('renders DashboardGrid', () => { ); - const panelElements = component.find('EmbeddableChildPanel'); expect(panelElements.length).toBe(2); }); -test('renders DashboardGrid with no visualizations', async () => { +test('renders DashboardGrid with no visualizations', () => { const { props, options } = prepare(); const component = mountWithIntl( @@ -114,12 +117,11 @@ test('renders DashboardGrid with no visualizations', async () => { ); props.container.updateInput({ panels: {} }); - await nextTick(); component.update(); expect(component.find('EmbeddableChildPanel').length).toBe(0); }); -test('DashboardGrid removes panel when removed from container', async () => { +test('DashboardGrid removes panel when removed from container', () => { const { props, options } = prepare(); const component = mountWithIntl( @@ -131,13 +133,12 @@ test('DashboardGrid removes panel when removed from container', async () => { const filteredPanels = { ...originalPanels }; delete filteredPanels['1']; props.container.updateInput({ panels: filteredPanels }); - await nextTick(); component.update(); const panelElements = component.find('EmbeddableChildPanel'); expect(panelElements.length).toBe(1); }); -test('DashboardGrid renders expanded panel', async () => { +test('DashboardGrid renders expanded panel', () => { const { props, options } = prepare(); const component = mountWithIntl( @@ -146,7 +147,6 @@ test('DashboardGrid renders expanded panel', async () => { ); props.container.updateInput({ expandedPanelId: '1' }); - await nextTick(); component.update(); // Both panels should still exist in the dom, so nothing needs to be re-fetched once minimized. expect(component.find('EmbeddableChildPanel').length).toBe(2); @@ -156,7 +156,6 @@ test('DashboardGrid renders expanded panel', async () => { ).toBe('1'); props.container.updateInput({ expandedPanelId: undefined }); - await nextTick(); component.update(); expect(component.find('EmbeddableChildPanel').length).toBe(2); diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx index 7b83407bf8063c..e3d9b8552f0606 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx @@ -56,10 +56,14 @@ function getProps( } as any, notifications: {} as any, overlays: {} as any, - inspector: {} as any, + inspector: { + isAvailable: jest.fn(), + } as any, SavedObjectFinder: () => null, ExitFullScreenButton, - uiActions: {} as any, + uiActions: { + getTriggerCompatibleActions: (() => []) as any, + } as any, }; const input = getSampleDashboardInput({ @@ -117,6 +121,24 @@ test('renders DashboardViewport with no visualizations', () => { component.unmount(); }); +test('renders DashboardEmptyScreen', () => { + const renderEmptyScreen = jest.fn(); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true }); + const component = mount( + + + + + + ); + const dashboardEmptyScreenDiv = component.find('.dshDashboardEmptyScreen'); + expect(dashboardEmptyScreenDiv.length).toBe(1); + expect(renderEmptyScreen).toHaveBeenCalled(); + + component.unmount(); +}); + test('renders exit full screen button when in full screen mode', async () => { const { props, options } = getProps(); props.container.updateInput({ isFullScreenMode: true }); @@ -149,6 +171,39 @@ test('renders exit full screen button when in full screen mode', async () => { component.unmount(); }); +test('renders exit full screen button when in full screen mode and empty screen', async () => { + const renderEmptyScreen = jest.fn(); + renderEmptyScreen.mockReturnValue(React.createElement('div')); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true, isFullScreenMode: true }); + const component = mount( + + + + + + ); + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).toBe('ExitFullScreenButton'); + + props.container.updateInput({ isFullScreenMode: false }); + component.update(); + await nextTick(); + + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).not.toBe('ExitFullScreenButton'); + + component.unmount(); +}); + test('DashboardViewport unmount unsubscribes', async done => { const { props, options } = getProps(); const component = mount( diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx index 13407e5e33725a..e7fd379898dd13 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx @@ -26,6 +26,7 @@ import { context } from '../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; + renderEmpty?: () => React.ReactNode; } interface State { @@ -34,6 +35,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; + isEmptyState?: boolean; } export class DashboardViewport extends React.Component { @@ -44,26 +46,40 @@ export class DashboardViewport extends React.Component { - const { isFullScreenMode, useMargins, title, description } = this.props.container.getInput(); + const { + isFullScreenMode, + useMargins, + title, + description, + isEmptyState, + } = this.props.container.getInput(); if (this.mounted) { this.setState({ isFullScreenMode, description, useMargins, title, + isEmptyState, }); } }); @@ -82,19 +98,33 @@ export class DashboardViewport extends React.Component + {isFullScreenMode && ( + + )} + {renderEmpty && renderEmpty()} +
    + ); + } + + private renderContainerScreen() { const { container } = this.props; + const { isFullScreenMode, panels, title, description, useMargins } = this.state; return (
    - {this.state.isFullScreenMode && ( + {isFullScreenMode && ( @@ -103,4 +133,13 @@ export class DashboardViewport extends React.Component ); } + + public render() { + return ( + + {this.state.isEmptyState ? this.renderEmptyScreen() : null} + {this.renderContainerScreen()} + + ); + } } diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx index 79cc9b6980545e..d18fbba239ec08 100644 --- a/src/plugins/dashboard_embeddable_container/public/plugin.tsx +++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx @@ -27,7 +27,7 @@ import { ExpandPanelAction, ReplacePanelAction } from '.'; import { DashboardContainerFactory } from './embeddable/dashboard_container_factory'; import { Start as InspectorStartContract } from '../../../plugins/inspector/public'; import { - SavedObjectFinder as SavedObjectFinderUi, + SavedObjectFinderUi, SavedObjectFinderProps, ExitFullScreenButton as ExitFullScreenButtonUi, ExitFullScreenButtonProps, diff --git a/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts b/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts new file mode 100644 index 00000000000000..1784a2650a95a9 --- /dev/null +++ b/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts @@ -0,0 +1,322 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 indexPatternResponse = { + id: 'logstash-*', + title: 'logstash-*', + 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' } }, + }, + ], +}; diff --git a/src/plugins/data/common/es_query/es_query/from_filters.test.ts b/src/plugins/data/common/es_query/es_query/from_filters.test.ts index 8c1d990c389b8f..a93a91a42dbf38 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.test.ts +++ b/src/plugins/data/common/es_query/es_query/from_filters.test.ts @@ -144,5 +144,30 @@ describe('build query', () => { expect(result.filter).toEqual(expectedESQueries); }); + + test('should wrap filters targeting nested fields in a nested query', () => { + const filters = [ + { + exists: { field: 'nestedField.child' }, + meta: { type: 'exists', alias: '', disabled: false, negate: false }, + }, + ]; + + const expectedESQueries = [ + { + nested: { + path: 'nestedField', + query: { + exists: { + field: 'nestedField.child', + }, + }, + }, + }, + ]; + + const result = buildQueryFromFilters(filters, indexPattern); + expect(result.filter).toEqual(expectedESQueries); + }); }); }); diff --git a/src/plugins/data/common/es_query/es_query/from_filters.ts b/src/plugins/data/common/es_query/es_query/from_filters.ts index e33040485bf47d..ed91d391fc1fda 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.ts +++ b/src/plugins/data/common/es_query/es_query/from_filters.ts @@ -21,6 +21,7 @@ import { migrateFilter } from './migrate_filter'; import { filterMatchesIndex } from './filter_matches_index'; import { Filter, cleanFilter, isFilterDisabled } from '../filters'; import { IIndexPattern } from '../../index_patterns'; +import { handleNestedFilter } from './handle_nested_filter'; /** * Create a filter that can be reversed for filters with negate set @@ -59,20 +60,22 @@ export const buildQueryFromFilters = ( ) => { filters = filters.filter(filter => filter && !isFilterDisabled(filter)); - return { - must: [], - filter: filters - .filter(filterNegate(false)) + const filtersToESQueries = (negate: boolean) => { + return filters + .filter(filterNegate(negate)) .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) + .map(filter => { + return migrateFilter(filter, indexPattern); + }) + .map(filter => handleNestedFilter(filter, indexPattern)) .map(translateToQuery) - .map(cleanFilter) - .map(filter => migrateFilter(filter, indexPattern)), + .map(cleanFilter); + }; + + return { + must: [], + filter: filtersToESQueries(false), should: [], - must_not: filters - .filter(filterNegate(true)) - .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) - .map(translateToQuery) - .map(cleanFilter) - .map(filter => migrateFilter(filter, indexPattern)), + must_not: filtersToESQueries(true), }; }; diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts index a4ab03687f92e9..d146d81973d0d7 100644 --- a/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts +++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; import { getEsQueryConfig } from './get_es_query_config'; -import { UiSettingsClientContract } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; const config = ({ get(item: string) { @@ -36,7 +36,7 @@ const config = ({ 'dateFormat:tz': { dateFormatTZ: 'Browser', }, -} as unknown) as UiSettingsClientContract; +} as unknown) as IUiSettingsClient; describe('getEsQueryConfig', () => { test('should return the parameters of an Elasticsearch query config requested', () => { diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts new file mode 100644 index 00000000000000..594b2641c39be7 --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.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 { handleNestedFilter } from './handle_nested_filter'; +import { fields } from '../../index_patterns/mocks'; +import { buildPhraseFilter, buildQueryFilter } from '../filters'; +import { IFieldType, IIndexPattern } from '../../index_patterns'; + +describe('handleNestedFilter', function() { + const indexPattern: IIndexPattern = ({ + id: 'logstash-*', + fields, + } as unknown) as IIndexPattern; + + it("should return the filter's query wrapped in nested query if the target field is nested", () => { + const field = getField('nestedField.child'); + const filter = buildPhraseFilter(field!, 'foo', indexPattern); + const result = handleNestedFilter(filter, indexPattern); + expect(result).toEqual({ + meta: { + index: 'logstash-*', + }, + nested: { + path: 'nestedField', + query: { + match_phrase: { + 'nestedField.child': 'foo', + }, + }, + }, + }); + }); + + it('should return filter untouched if it does not target a nested field', () => { + const field = getField('extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = handleNestedFilter(filter, indexPattern); + expect(result).toBe(filter); + }); + + it('should return filter untouched if it does not target a field from the given index pattern', () => { + const field = { ...getField('extension'), name: 'notarealfield' }; + const filter = buildPhraseFilter(field as IFieldType, 'jpg', indexPattern); + const result = handleNestedFilter(filter, indexPattern); + expect(result).toBe(filter); + }); + + it('should return filter untouched if no index pattern is provided', () => { + const field = getField('extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = handleNestedFilter(filter); + expect(result).toBe(filter); + }); + + it('should return the filter untouched if a target field cannot be determined', () => { + // for example, we don't support query_string queries + const filter = buildQueryFilter( + { + query: { + query_string: { + query: 'response:200', + }, + }, + }, + 'logstash-*', + 'foo' + ); + const result = handleNestedFilter(filter); + expect(result).toBe(filter); + }); + + function getField(name: string) { + return indexPattern.fields.find(field => field.name === name); + } +}); diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts new file mode 100644 index 00000000000000..27be7925fe00cc --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/handle_nested_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 { getFilterField, cleanFilter, Filter } from '../filters'; +import { IIndexPattern } from '../../index_patterns'; + +export const handleNestedFilter = (filter: Filter, indexPattern?: IIndexPattern) => { + if (!indexPattern) return filter; + + const fieldName = getFilterField(filter); + if (!fieldName) { + return filter; + } + + const field = indexPattern.fields.find(indexPatternField => indexPatternField.name === fieldName); + if (!field || !field.subType || !field.subType.nested || !field.subType.nested.path) { + return filter; + } + + const query = cleanFilter(filter); + + return { + meta: filter.meta, + nested: { + path: field.subType.nested.path, + query: query.query || query, + }, + }; +}; diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts index e01240da87543b..698d7bb48e685d 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts @@ -23,26 +23,32 @@ import { PhraseFilter, MatchAllFilter } from '../filters'; describe('migrateFilter', function() { const oldMatchPhraseFilter = ({ - match: { - fieldFoo: { - query: 'foobar', - type: 'phrase', + query: { + match: { + fieldFoo: { + query: 'foobar', + type: 'phrase', + }, }, }, + meta: {}, } as unknown) as DeprecatedMatchPhraseFilter; const newMatchPhraseFilter = ({ - match_phrase: { - fieldFoo: { - query: 'foobar', + query: { + match_phrase: { + fieldFoo: { + query: 'foobar', + }, }, }, + meta: {}, } as unknown) as PhraseFilter; it('should migrate match filters of type phrase', function() { const migratedFilter = migrateFilter(oldMatchPhraseFilter, undefined); - expect(isEqual(migratedFilter, newMatchPhraseFilter)).toBe(true); + expect(migratedFilter).toEqual(newMatchPhraseFilter); }); it('should not modify the original filter', function() { diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.ts index fdc40768ebe41c..22fbfe0e1ab083 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.ts @@ -22,31 +22,27 @@ import { getConvertedValueForField } from '../filters'; import { Filter } from '../filters'; import { IIndexPattern } from '../../index_patterns'; -/** @deprecated - * see https://github.com/elastic/elasticsearch/pull/17508 - * */ export interface DeprecatedMatchPhraseFilter extends Filter { - match: { - [field: string]: { - query: any; - type: 'phrase'; + query: { + match: { + [field: string]: { + query: any; + type: 'phrase'; + }; }; }; } -/** @deprecated - * see https://github.com/elastic/elasticsearch/pull/17508 - * */ -function isMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter { - const fieldName = filter.match && Object.keys(filter.match)[0]; +function isDeprecatedMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter { + const fieldName = filter.query && filter.query.match && Object.keys(filter.query.match)[0]; - return Boolean(fieldName && get(filter, ['match', fieldName, 'type']) === 'phrase'); + return Boolean(fieldName && get(filter, ['query', 'match', fieldName, 'type']) === 'phrase'); } export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) { - if (isMatchPhraseFilter(filter)) { - const fieldName = Object.keys(filter.match)[0]; - const params: Record = get(filter, ['match', fieldName]); + if (isDeprecatedMatchPhraseFilter(filter)) { + const fieldName = Object.keys(filter.query.match)[0]; + const params: Record = get(filter, ['query', 'match', fieldName]); if (indexPattern) { const field = indexPattern.fields.find(f => f.name === fieldName); @@ -55,8 +51,11 @@ export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) { } } return { - match_phrase: { - [fieldName]: omit(params, 'type'), + ...filter, + query: { + match_phrase: { + [fieldName]: omit(params, 'type'), + }, }, }; } diff --git a/src/plugins/data/common/es_query/filters/exists_filter.test.ts b/src/plugins/data/common/es_query/filters/exists_filter.test.ts new file mode 100644 index 00000000000000..af52192dd85e4a --- /dev/null +++ b/src/plugins/data/common/es_query/filters/exists_filter.test.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 { buildExistsFilter, getExistsFilterField } from './exists_filter'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/fields/fields.mocks.ts'; + +describe('exists filter', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + describe('getExistsFilterField', function() { + it('should return the name of the field an exists query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildExistsFilter(field!, indexPattern); + const result = getExistsFilterField(filter); + expect(result).toBe('extension'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts index a20a4f0634766d..035983dc446dc3 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.ts @@ -33,6 +33,10 @@ export type ExistsFilter = Filter & { export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists; +export const getExistsFilterField = (filter: ExistsFilter) => { + return filter.exists && filter.exists.field; +}; + export const buildExistsFilter = (field: IFieldType, indexPattern: IIndexPattern) => { return { meta: { diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts new file mode 100644 index 00000000000000..63c3a59044c1f2 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/geo_bounding_box_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 { getGeoBoundingBoxFilterField } from './geo_bounding_box_filter'; + +describe('geo_bounding_box filter', function() { + describe('getGeoBoundingBoxFilterField', function() { + it('should return the name of the field a geo_bounding_box query is targeting', () => { + const filter = { + geo_bounding_box: { + geoPointField: { + bottom_right: { lat: 1, lon: 1 }, + top_left: { lat: 1, lon: 1 }, + }, + ignore_unmapped: true, + }, + meta: { + disabled: false, + negate: false, + alias: null, + params: { + bottom_right: { lat: 1, lon: 1 }, + top_left: { lat: 1, lon: 1 }, + }, + }, + }; + const result = getGeoBoundingBoxFilterField(filter); + expect(result).toBe('geoPointField'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts index f4673af96b2cd6..619903954ff551 100644 --- a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts +++ b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts @@ -33,3 +33,10 @@ export type GeoBoundingBoxFilter = Filter & { export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFilter => filter && filter.geo_bounding_box; + +export const getGeoBoundingBoxFilterField = (filter: GeoBoundingBoxFilter) => { + return ( + filter.geo_bounding_box && + Object.keys(filter.geo_bounding_box).find(key => key !== 'ignore_unmapped') + ); +}; diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts new file mode 100644 index 00000000000000..ba8e43b0cea856 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.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 { getGeoPolygonFilterField } from './geo_polygon_filter'; + +describe('geo_polygon filter', function() { + describe('getGeoPolygonFilterField', function() { + it('should return the name of the field a geo_polygon query is targeting', () => { + const filter = { + geo_polygon: { + geoPointField: { + points: [{ lat: 1, lon: 1 }], + }, + ignore_unmapped: true, + }, + meta: { + disabled: false, + negate: false, + alias: null, + params: { + points: [{ lat: 1, lon: 1 }], + }, + }, + }; + const result = getGeoPolygonFilterField(filter); + expect(result).toBe('geoPointField'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts index 4cf82a92d2cef4..03367feb83ee4e 100644 --- a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts +++ b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts @@ -32,3 +32,9 @@ export type GeoPolygonFilter = Filter & { export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter => filter && filter.geo_polygon; + +export const getGeoPolygonFilterField = (filter: GeoPolygonFilter) => { + return ( + filter.geo_polygon && Object.keys(filter.geo_polygon).find(key => key !== 'ignore_unmapped') + ); +}; diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.test.ts b/src/plugins/data/common/es_query/filters/get_filter_field.test.ts new file mode 100644 index 00000000000000..2fc8ffef9713b5 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/get_filter_field.test.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 { buildPhraseFilter } from './phrase_filter'; +import { buildQueryFilter } from './query_string_filter'; +import { getFilterField } from './get_filter_field'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/fields/fields.mocks.ts'; + +describe('getFilterField', function() { + const indexPattern: IIndexPattern = ({ + id: 'logstash-*', + fields, + } as unknown) as IIndexPattern; + + it('should return the field name from known filter types that target a specific field', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = getFilterField(filter); + expect(result).toBe('extension'); + }); + + it('should return undefined for filters that do not target a specific field', () => { + const filter = buildQueryFilter( + { + query: { + query_string: { + query: 'response:200 and extension:jpg', + }, + }, + }, + indexPattern.id!, + '' + ); + const result = getFilterField(filter); + expect(result).toBe(undefined); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.ts b/src/plugins/data/common/es_query/filters/get_filter_field.ts new file mode 100644 index 00000000000000..dfb575157d3620 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/get_filter_field.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 { Filter } from './meta_filter'; +import { getExistsFilterField, isExistsFilter } from './exists_filter'; +import { getGeoBoundingBoxFilterField, isGeoBoundingBoxFilter } from './geo_bounding_box_filter'; +import { getGeoPolygonFilterField, isGeoPolygonFilter } from './geo_polygon_filter'; +import { getPhraseFilterField, isPhraseFilter } from './phrase_filter'; +import { getPhrasesFilterField, isPhrasesFilter } from './phrases_filter'; +import { getRangeFilterField, isRangeFilter } from './range_filter'; +import { getMissingFilterField, isMissingFilter } from './missing_filter'; + +export const getFilterField = (filter: Filter) => { + if (isExistsFilter(filter)) { + return getExistsFilterField(filter); + } + if (isGeoBoundingBoxFilter(filter)) { + return getGeoBoundingBoxFilterField(filter); + } + if (isGeoPolygonFilter(filter)) { + return getGeoPolygonFilterField(filter); + } + if (isPhraseFilter(filter)) { + return getPhraseFilterField(filter); + } + if (isPhrasesFilter(filter)) { + return getPhrasesFilterField(filter); + } + if (isRangeFilter(filter)) { + return getRangeFilterField(filter); + } + if (isMissingFilter(filter)) { + return getMissingFilterField(filter); + } + + return; +}; diff --git a/src/plugins/data/common/es_query/filters/index.ts b/src/plugins/data/common/es_query/filters/index.ts index 1bd534bf74ff7c..403ff2b79b55f9 100644 --- a/src/plugins/data/common/es_query/filters/index.ts +++ b/src/plugins/data/common/es_query/filters/index.ts @@ -22,6 +22,7 @@ import { Filter } from './meta_filter'; export * from './build_filters'; export * from './get_filter_params'; +export * from './get_filter_field'; export * from './custom_filter'; export * from './exists_filter'; diff --git a/src/plugins/data/common/es_query/filters/missing_filter.test.ts b/src/plugins/data/common/es_query/filters/missing_filter.test.ts new file mode 100644 index 00000000000000..240d8fb26f3e0a --- /dev/null +++ b/src/plugins/data/common/es_query/filters/missing_filter.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 { getMissingFilterField } from './missing_filter'; + +describe('missing filter', function() { + describe('getMissingFilterField', function() { + it('should return the name of the field an missing query is targeting', () => { + const filter = { + missing: { + field: 'extension', + }, + meta: { + disabled: false, + negate: false, + alias: null, + }, + }; + const result = getMissingFilterField(filter); + expect(result).toBe('extension'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/missing_filter.ts b/src/plugins/data/common/es_query/filters/missing_filter.ts index 5411187cbcfd73..c8e1194a8f3cc4 100644 --- a/src/plugins/data/common/es_query/filters/missing_filter.ts +++ b/src/plugins/data/common/es_query/filters/missing_filter.ts @@ -27,3 +27,7 @@ export type MissingFilter = Filter & { }; export const isMissingFilter = (filter: any): filter is MissingFilter => filter && filter.missing; + +export const getMissingFilterField = (filter: MissingFilter) => { + return filter.missing && filter.missing.field; +}; 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 index 3c7d00a80fecf8..9f90097e55475c 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts @@ -17,8 +17,12 @@ * under the License. */ -import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter'; -import { getField } from '../../index_patterns/mocks'; +import { + buildInlineScriptForPhraseFilter, + buildPhraseFilter, + getPhraseFilterField, +} from './phrase_filter'; +import { fields, getField } from '../../index_patterns/mocks'; import { IIndexPattern } from '../../index_patterns'; describe('Phrase filter builder', () => { @@ -95,3 +99,16 @@ describe('buildInlineScriptForPhraseFilter', () => { expect(buildInlineScriptForPhraseFilter(field)).toBe(expected); }); }); + +describe('getPhraseFilterField', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + it('should return the name of the field a phrase query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = getPhraseFilterField(filter); + expect(result).toBe('extension'); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.test.ts b/src/plugins/data/common/es_query/filters/phrases_filter.test.ts new file mode 100644 index 00000000000000..3a121eb9da034a --- /dev/null +++ b/src/plugins/data/common/es_query/filters/phrases_filter.test.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 { buildPhrasesFilter, getPhrasesFilterField } from './phrases_filter'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/fields/fields.mocks.ts'; + +describe('phrases filter', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + describe('getPhrasesFilterField', function() { + it('should return the name of the field a phrases query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildPhrasesFilter(field!, ['jpg', 'png'], indexPattern); + const result = getPhrasesFilterField(filter); + expect(result).toBe('extension'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts index f7164f0ad3c836..006e0623be9139 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -32,7 +32,13 @@ export type PhrasesFilter = Filter & { }; export const isPhrasesFilter = (filter: any): filter is PhrasesFilter => - filter && filter.meta.type === FILTERS.PHRASES; + filter?.meta?.type === FILTERS.PHRASES; + +export const getPhrasesFilterField = (filter: PhrasesFilter) => { + // Phrases is a newer filter type that has always been created via a constructor that ensures + // `meta.key` is set to the field name + return filter.meta.key; +}; // Creates a filter where the given field matches one or more of the given values // params should be an array of values 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 index 4fcb15ccac44a4..18285194c60548 100644 --- 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 @@ -19,7 +19,7 @@ import { buildQueryFilter } from './query_string_filter'; -describe('Phrase filter builder', () => { +describe('Query string filter builder', () => { it('should be a function', () => { expect(typeof buildQueryFilter).toBe('function'); }); 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 index 56b63018b51533..45d59c97941b3c 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -18,8 +18,8 @@ */ import { each } from 'lodash'; -import { buildRangeFilter, RangeFilter } from './range_filter'; -import { getField } from '../../index_patterns/mocks'; +import { buildRangeFilter, getRangeFilterField, RangeFilter } from './range_filter'; +import { fields, getField } from '../../index_patterns/mocks'; import { IIndexPattern, IFieldType } from '../../index_patterns'; describe('Range filter builder', () => { @@ -172,3 +172,16 @@ describe('Range filter builder', () => { }); }); }); + +describe('getRangeFilterField', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + test('should return the name of the field a range query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'bytes'); + const filter = buildRangeFilter(field!, {}, indexPattern); + const result = getRangeFilterField(filter); + expect(result).toBe('bytes'); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index 3d819bd145fa63..b300539f4280a5 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -88,6 +88,10 @@ export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => { return hasRangeKeys(params); }; +export const getRangeFilterField = (filter: RangeFilter) => { + return filter.range && Object.keys(filter.range)[0]; +}; + const formatValue = (field: IFieldType, params: any[]) => map(params, (val: any, key: string) => get(operators, key) + format(field, val)).join(' '); diff --git a/src/plugins/data/common/es_query/kuery/functions/nested.js b/src/plugins/data/common/es_query/kuery/functions/nested.js index 6237189e16311b..d1de09b977f603 100644 --- a/src/plugins/data/common/es_query/kuery/functions/nested.js +++ b/src/plugins/data/common/es_query/kuery/functions/nested.js @@ -28,10 +28,6 @@ export function buildNodeParams(path, 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; diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index 54977c7e669761..c9906fb1360524 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -20,8 +20,7 @@ import { template, escape, keys } from 'lodash'; // @ts-ignore import { noWhiteSpace } from '../../../../../legacy/core_plugins/kibana/common/utils/no_white_space'; -// @ts-ignore -import { shortenDottedString } from '../../../../../legacy/core_plugins/kibana/common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../utils'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts index 0edd219ca60f94..b2d92cf475a161 100644 --- a/src/plugins/data/common/field_formats/converters/string.ts +++ b/src/plugins/data/common/field_formats/converters/string.ts @@ -22,8 +22,7 @@ import { asPrettyString } from '../index'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; -// @ts-ignore -import { shortenDottedString } from '../../../../../legacy/core_plugins/kibana/common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../utils'; const TRANSFORM_OPTIONS = [ { diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index f9bbeb5f4b3f39..b334342a57ec6b 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -22,5 +22,5 @@ export * from './field_formats'; export * from './kbn_field_types'; export * from './index_patterns'; export * from './es_query'; - +export * from './utils'; export * from './types'; diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts new file mode 100644 index 00000000000000..7196c96989e97d --- /dev/null +++ b/src/plugins/data/common/utils/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { shortenDottedString } from './shorten_dotted_string'; diff --git a/src/plugins/data/common/utils/shorten_dotted_string.test.ts b/src/plugins/data/common/utils/shorten_dotted_string.test.ts new file mode 100644 index 00000000000000..5f8d084ce5e88c --- /dev/null +++ b/src/plugins/data/common/utils/shorten_dotted_string.test.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 { shortenDottedString } from './shorten_dotted_string'; + +describe('shortenDottedString', () => { + test('should convert a dot.notated.string into a short string', () => { + expect(shortenDottedString('dot.notated.string')).toBe('d.n.string'); + }); + + test('should ignore non-string values', () => { + const obj = { key: 'val' }; + + expect(shortenDottedString(true)).toBe(true); + expect(shortenDottedString(123)).toBe(123); + expect(shortenDottedString(obj)).toBe(obj); + }); +}); diff --git a/src/plugins/data/common/utils/shorten_dotted_string.ts b/src/plugins/data/common/utils/shorten_dotted_string.ts new file mode 100644 index 00000000000000..379413c0d91c8f --- /dev/null +++ b/src/plugins/data/common/utils/shorten_dotted_string.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. + */ + +const DOT_PREFIX_RE = /(.).+?\./g; + +/** + * Convert a dot.notated.string into a short + * version (d.n.string) + * + * @return {any} + */ +export function shortenDottedString(input: any) { + return typeof input !== 'string' ? input : input.replace(DOT_PREFIX_RE, '$1.'); +} diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index bd2bc1bab2a1a0..998eaa55858d22 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -2,5 +2,6 @@ "id": "data", "version": "kibana", "server": true, - "ui": true + "ui": true, + "requiredPlugins": ["uiActions"] } diff --git a/src/plugins/data/public/_index.scss b/src/plugins/data/public/_index.scss new file mode 100644 index 00000000000000..a51fde079f10b0 --- /dev/null +++ b/src/plugins/data/public/_index.scss @@ -0,0 +1 @@ +@import './ui/index'; diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts similarity index 83% rename from src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts rename to src/plugins/data/public/actions/apply_filter_action.ts index 946b3997a97129..b006889637c507 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -18,22 +18,18 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreStart } from 'src/core/public'; -import { toMountPoint } from '../../../../../../plugins/kibana_react/public'; -import { - IAction, - createAction, - IncompatibleActionError, -} from '../../../../../../plugins/ui_actions/public'; +import { toMountPoint } from '../../../kibana_react/public'; +import { IAction, createAction, IncompatibleActionError } from '../../../ui_actions/public'; +import { getOverlays, getIndexPatterns } from '../services'; +import { applyFiltersPopover } from '../ui/apply_filters'; import { esFilters, FilterManager, TimefilterContract, - applyFiltersPopover, changeTimeFilter, extractTimeFilter, -} from '../../../../../../plugins/data/public'; -import { IndexPatternsStart } from '../../index_patterns'; +} from '..'; + export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION'; interface ActionContext { @@ -46,10 +42,8 @@ async function isCompatible(context: ActionContext) { } export function createFilterAction( - overlays: CoreStart['overlays'], filterManager: FilterManager, - timeFilter: TimefilterContract, - indexPatternsService: IndexPatternsStart + timeFilter: TimefilterContract ): IAction { return createAction({ type: GLOBAL_APPLY_FILTER_ACTION, @@ -74,12 +68,12 @@ export function createFilterAction( if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( filters.map(filter => { - return indexPatternsService.indexPatterns.get(filter.meta.index); + return getIndexPatterns().get(filter.meta.index!); }) ); const filterSelectionPromise: Promise = new Promise(resolve => { - const overlay = overlays.openModal( + const overlay = getOverlays().openModal( toMountPoint( applyFiltersPopover( filters, diff --git a/src/plugins/data/public/actions/index.ts b/src/plugins/data/public/actions/index.ts new file mode 100644 index 00000000000000..5d469606944a18 --- /dev/null +++ b/src/plugins/data/public/actions/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 { GLOBAL_APPLY_FILTER_ACTION, createFilterAction } from './apply_filter_action'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete_provider/types.ts index 3d34b1bc4a2d2a..389057f94144d2 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete_provider/types.ts @@ -40,6 +40,7 @@ export type GetSuggestions = (args: { query: string; selectionStart: number; selectionEnd: number; + signal?: AbortSignal; }) => Promise; /** @public **/ diff --git a/src/plugins/data/public/field_formats_provider/field_formats.ts b/src/plugins/data/public/field_formats_provider/field_formats.ts index f46994c209dedc..20e90b8e4a5458 100644 --- a/src/plugins/data/public/field_formats_provider/field_formats.ts +++ b/src/plugins/data/public/field_formats_provider/field_formats.ts @@ -18,7 +18,7 @@ */ import { forOwn, isFunction, memoize } from 'lodash'; -import { UiSettingsClientContract } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, @@ -31,7 +31,7 @@ import { FieldType } from './types'; export class FieldFormatRegisty { private fieldFormats: Map; - private uiSettings!: UiSettingsClientContract; + private uiSettings!: IUiSettingsClient; private defaultMap: Record; constructor() { @@ -41,7 +41,7 @@ export class FieldFormatRegisty { getConfig = (key: string, override?: any) => this.uiSettings.get(key, override); - init(uiSettings: UiSettingsClientContract) { + init(uiSettings: IUiSettingsClient) { this.uiSettings = uiSettings; this.parseDefaultTypeMap(this.uiSettings.get('format:defaultTypeMap')); diff --git a/src/plugins/data/public/field_formats_provider/field_formats_service.ts b/src/plugins/data/public/field_formats_provider/field_formats_service.ts index b144ea7ec25304..ea1a8af2930b01 100644 --- a/src/plugins/data/public/field_formats_provider/field_formats_service.ts +++ b/src/plugins/data/public/field_formats_provider/field_formats_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiSettingsClientContract } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { FieldFormatRegisty } from './field_formats'; import { @@ -43,7 +43,7 @@ import { * @internal */ interface FieldFormatsServiceDependencies { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } export class FieldFormatsService { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ace0b44378b459..e54278698a05a8 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -18,18 +18,15 @@ */ import { PluginInitializerContext } from '../../../core/public'; -import { DataPublicPlugin } from './plugin'; - export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } -export { DataPublicPlugin as Plugin }; - export * from '../common'; export * from './autocomplete_provider'; export * from './field_formats_provider'; +export * from './index_patterns'; export * from './types'; @@ -38,3 +35,7 @@ export * from './search'; export * from './query'; export * from './ui'; + +// Export plugin after all other imports +import { DataPublicPlugin } from './plugin'; +export { DataPublicPlugin as Plugin }; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts similarity index 92% rename from src/legacy/core_plugins/data/public/index_patterns/fields/field.ts rename to src/plugins/data/public/index_patterns/fields/field.ts index 91964655f6f3e7..c8c8ac1ffd3214 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -20,26 +20,19 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; -// @ts-ignore -import { shortenDottedString } from '../../../../../core_plugins/kibana/common/utils/shorten_dotted_string'; import { IndexPattern } from '../index_patterns'; -import { getNotifications, getFieldFormats } from '../services'; - +import { getNotifications, getFieldFormats } from '../../services'; import { - FieldFormat, - getKbnFieldType, IFieldType, + getKbnFieldType, IFieldSubType, -} from '../../../../../../plugins/data/public'; + FieldFormat, + shortenDottedString, +} from '../../../common'; export type FieldSpec = Record; -/** @deprecated - * Please use IFieldType instead - * */ -export type FieldType = IFieldType; - -export class Field implements FieldType { +export class Field implements IFieldType { name: string; type: string; script?: string; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts similarity index 86% rename from src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts rename to src/plugins/data/public/index_patterns/fields/field_list.ts index 108aacc8e07def..03214a8c96427a 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -19,18 +19,19 @@ import { findIndex } from 'lodash'; import { IndexPattern } from '../index_patterns'; -import { Field, FieldType, FieldSpec } from './field'; +import { IFieldType } from '../../../common'; +import { Field, FieldSpec } from './field'; type FieldMap = Map; -export interface FieldListInterface extends Array { +export interface IFieldList extends Array { getByName(name: Field['name']): Field | undefined; getByType(type: Field['type']): Field[]; add(field: FieldSpec): void; - remove(field: FieldType): void; + remove(field: IFieldType): void; } -export class FieldList extends Array implements FieldListInterface { +export class FieldList extends Array implements IFieldList { private byName: FieldMap = new Map(); private groups: Map = new Map(); private indexPattern: IndexPattern; @@ -42,7 +43,7 @@ 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); + private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); constructor(indexPattern: IndexPattern, specs: FieldSpec[] = [], shortDotsEnable = false) { super(); @@ -61,7 +62,7 @@ export class FieldList extends Array implements FieldListInterface { this.setByGroup(newField); }; - remove = (field: FieldType) => { + remove = (field: IFieldType) => { this.removeByGroup(field); this.byName.delete(field.name); diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/index.ts b/src/plugins/data/public/index_patterns/fields/index.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/fields/index.ts rename to src/plugins/data/public/index_patterns/fields/index.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.js b/src/plugins/data/public/index_patterns/fields/obj_define.js similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.js rename to src/plugins/data/public/index_patterns/fields/obj_define.js diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.test.js b/src/plugins/data/public/index_patterns/fields/obj_define.test.js similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.test.js rename to src/plugins/data/public/index_patterns/fields/obj_define.test.js diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts new file mode 100644 index 00000000000000..6f4821c3917212 --- /dev/null +++ b/src/plugins/data/public/index_patterns/index.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 { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + IndexPatternMissingIndices, + validateIndexPattern, + getFromSavedObject, +} from './lib'; +import { getRoutes } from './utils'; +import { flattenHitWrapper, formatHitProvider } from './index_patterns'; + +export const indexPatterns = { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + IndexPatternMissingIndices, + validate: validateIndexPattern, + getRoutes, + getFromSavedObject, + flattenHitWrapper, + formatHitProvider, +}; + +export { Field, FieldList, IFieldList } from './fields'; + +// TODO: figure out how to replace IndexPatterns in get_inner_angular. +export { IndexPattern, IndexPatterns, IndexPatternsContract } from './index_patterns'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts b/src/plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts rename to src/plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts b/src/plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts similarity index 97% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts rename to src/plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts index a3653bb529fa38..eb6c69b4143160 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts @@ -19,7 +19,7 @@ import { IndexPattern } from './index_pattern'; -export interface PatternCache { +interface PatternCache { get: (id: string) => IndexPattern; set: (id: string, value: IndexPattern) => IndexPattern; clear: (id: string) => void; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts rename to src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts rename to src/plugins/data/public/index_patterns/index_patterns/format_hit.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index_patterns/index.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index.ts rename to src/plugins/data/public/index_patterns/index_patterns/index.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts similarity index 94% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index ee9f9b493ebf25..f56f94fa8c2605 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -20,24 +20,23 @@ import { defaults, pluck, last, get } from 'lodash'; import { IndexPattern } from './index_pattern'; -import { DuplicateField } from '../../../../../../plugins/kibana_utils/public'; +import { DuplicateField } from '../../../../kibana_utils/public'; // @ts-ignore -import mockLogStashFields from '../../../../../../fixtures/logstash_fields'; +import mockLogStashFields from '../../../../../fixtures/logstash_fields'; // @ts-ignore - -import { stubbedSavedObjectIndexPattern } from '../../../../../../fixtures/stubbed_saved_object_index_pattern'; -import { Field } from '../index_patterns_service'; -import { setNotifications, setFieldFormats } from '../services'; +import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; +import { Field } from '../fields'; +import { setNotifications, setFieldFormats } 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'; -import { FieldFormatRegisty } from '../../../../../../plugins/data/public'; +import { notificationServiceMock } from '../../../../../core/public/notifications/notifications_service.mock'; +import { FieldFormatRegisty } from '../../field_formats_provider'; jest.mock('ui/new_platform'); -jest.mock('../../../../../../plugins/kibana_utils/public', () => { - const originalModule = jest.requireActual('../../../../../../plugins/kibana_utils/public'); +jest.mock('../../../../kibana_utils/public', () => { + const originalModule = jest.requireActual('../../../../kibana_utils/public'); return { ...originalModule, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts similarity index 94% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index f77342c7bc2744..19e465104cf4cd 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -26,31 +26,22 @@ import { expandShorthand, FieldMappingSpec, MappingObject, -} from '../../../../../../plugins/kibana_utils/public'; +} from '../../../../kibana_utils/public'; -import { - ES_FIELD_TYPES, - KBN_FIELD_TYPES, - IIndexPattern, -} from '../../../../../../plugins/data/public'; - -import { findIndexPatternByTitle, getRoutes } from '../utils'; -import { IndexPatternMissingIndices } from '../errors'; -import { Field, FieldList, FieldListInterface, FieldType } from '../fields'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; + +import { findByTitle, getRoutes } from '../utils'; +import { indexPatterns } from '../'; +import { Field, FieldList, IFieldList } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; -import { getNotifications, getFieldFormats } from '../services'; +import { getNotifications, getFieldFormats } from '../../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; -/** @deprecated - * Please use IIndexPattern instead - * */ -export type StaticIndexPattern = IIndexPattern; - export class IndexPattern implements IIndexPattern { [key: string]: any; @@ -59,7 +50,7 @@ export class IndexPattern implements IIndexPattern { public type?: string; public fieldFormatMap: any; public typeMeta: any; - public fields: FieldListInterface; + public fields: IFieldList; public timeFieldName: string | undefined; public formatHit: any; public formatField: any; @@ -296,7 +287,7 @@ export class IndexPattern implements IIndexPattern { await this.save(); } - removeScriptedField(field: FieldType) { + removeScriptedField(field: IFieldType) { this.fields.remove(field); return this.save(); } @@ -384,10 +375,7 @@ export class IndexPattern implements IIndexPattern { return response.id; }; - const potentialDuplicateByTitle = await findIndexPatternByTitle( - this.savedObjectsClient, - this.title - ); + const potentialDuplicateByTitle = await findByTitle(this.savedObjectsClient, this.title); // If there is potentially duplicate title, just create it if (!potentialDuplicateByTitle) { return await _create(); @@ -499,7 +487,7 @@ export class IndexPattern implements IIndexPattern { // so do not rethrow the error here const { toasts } = getNotifications(); - if (err instanceof IndexPatternMissingIndices) { + if (err instanceof indexPatterns.IndexPatternMissingIndices) { toasts.addDanger((err as any).message); return []; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts similarity index 87% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index 0a5d1bfcae21f9..591290065d0245 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -19,15 +19,7 @@ // eslint-disable-next-line max-classes-per-file import { IndexPatterns } from './index_patterns'; -import { - SavedObjectsClientContract, - UiSettingsClientContract, - HttpServiceBase, -} from 'kibana/public'; - -jest.mock('../errors', () => ({ - IndexPatternMissingIndices: jest.fn(), -})); +import { SavedObjectsClientContract, IUiSettingsClient, HttpServiceBase } from 'kibana/public'; jest.mock('./index_pattern', () => { class IndexPattern { @@ -56,7 +48,7 @@ describe('IndexPatterns', () => { beforeEach(() => { const savedObjectsClient = {} as SavedObjectsClientContract; - const uiSettings = {} as UiSettingsClientContract; + const uiSettings = {} as IUiSettingsClient; const http = {} as HttpServiceBase; indexPatterns = new IndexPatterns(uiSettings, savedObjectsClient, http); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts similarity index 91% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index c8e80b3aede20e..da58881b5b96e8 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -17,11 +17,10 @@ * under the License. */ -import { idx } from '@kbn/elastic-idx'; import { SavedObjectsClientContract, SimpleSavedObject, - UiSettingsClientContract, + IUiSettingsClient, HttpServiceBase, } from 'src/core/public'; @@ -32,13 +31,13 @@ import { IndexPatternsApiClient, GetFieldsOptions } from './index_patterns_api_c const indexPatternCache = createIndexPatternCache(); export class IndexPatterns { - private config: UiSettingsClientContract; + private config: IUiSettingsClient; private savedObjectsClient: SavedObjectsClientContract; private savedObjectsCache?: Array>> | null; private apiClient: IndexPatternsApiClient; constructor( - config: UiSettingsClientContract, + config: IUiSettingsClient, savedObjectsClient: SavedObjectsClientContract, http: HttpServiceBase ) { @@ -64,7 +63,7 @@ export class IndexPatterns { if (!this.savedObjectsCache) { return []; } - return this.savedObjectsCache.map(obj => idx(obj, _ => _.id)); + return this.savedObjectsCache.map(obj => obj?.id); }; getTitles = async (refresh: boolean = false): Promise => { @@ -74,7 +73,7 @@ export class IndexPatterns { if (!this.savedObjectsCache) { return []; } - return this.savedObjectsCache.map(obj => idx(obj, _ => _.attributes.title)); + return this.savedObjectsCache.map(obj => obj?.attributes?.title); }; getFields = async (fields: string[], refresh: boolean = false) => { @@ -86,7 +85,7 @@ export class IndexPatterns { } return this.savedObjectsCache.map((obj: Record) => { const result: Record = {}; - fields.forEach((f: string) => (result[f] = obj[f] || idx(obj, _ => _.attributes[f]))); + fields.forEach((f: string) => (result[f] = obj[f] || obj?.attributes?.[f])); return result; }); }; @@ -146,3 +145,5 @@ export class IndexPatterns { return indexPattern.init(); }; } + +export type IndexPatternsContract = PublicMethodsOf; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts similarity index 92% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts index 06933dc4090528..2d3e357e968199 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { setup } from '../../../../../../test_utils/public/http_test_setup'; +import { setup } from 'test_utils/http_test_setup'; export const { http } = setup(injectedMetadata => { injectedMetadata.getBasePath.mockReturnValue('/hola/daro/'); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts similarity index 95% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index c0e8516a75bb35..961e519338ac4d 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -18,7 +18,7 @@ */ import { HttpServiceBase } from 'src/core/public'; -import { IndexPatternMissingIndices } from '../errors'; +import { indexPatterns } from '../'; const API_BASE_URL: string = `/api/index_patterns/`; @@ -46,7 +46,7 @@ export class IndexPatternsApiClient { }) .catch((resp: any) => { if (resp.body.statusCode === 404 && resp.body.statuscode === 'no_matching_indices') { - throw new IndexPatternMissingIndices(resp.body.message); + throw new indexPatterns.IndexPatternMissingIndices(resp.body.message); } throw new Error(resp.body.message || resp.body.error || `${resp.body.statusCode} Response`); diff --git a/src/plugins/data/public/index_patterns/lib/errors.ts b/src/plugins/data/public/index_patterns/lib/errors.ts new file mode 100644 index 00000000000000..12efab7a2ca40d --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/errors.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. + */ + +/* eslint-disable */ + +import { KbnError } from '../../../../kibana_utils/public'; + +/** + * Tried to call a method that relies on SearchSource having an indexPattern assigned + */ +export class IndexPatternMissingIndices extends KbnError { + constructor(message: string) { + const defaultMessage = "IndexPattern's configured pattern does not match any indices"; + + super( + message && message.length ? `No matching indices found: ${message}` : defaultMessage + ); + } +} diff --git a/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts b/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts new file mode 100644 index 00000000000000..0faf6f4a103469 --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/get_from_saved_object.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 { get } from 'lodash'; + +export function getFromSavedObject(savedObject: any) { + if (get(savedObject, 'attributes.fields') === undefined) { + return; + } + + return { + id: savedObject.id, + fields: JSON.parse(savedObject.attributes.fields), + title: savedObject.attributes.title, + }; +} diff --git a/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts b/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts deleted file mode 100644 index 777a12c7e2884a..00000000000000 --- a/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts +++ /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 { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; - -export async function getIndexPatternTitle( - client: SavedObjectsClientContract, - indexPatternId: string -): Promise> { - const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject; - - if (savedObject.error) { - throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`); - } - - return savedObject.attributes.title; -} diff --git a/src/plugins/data/public/index_patterns/lib/get_title.ts b/src/plugins/data/public/index_patterns/lib/get_title.ts new file mode 100644 index 00000000000000..320205f5139c98 --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/get_title.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 { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; + +export async function getTitle( + client: SavedObjectsClientContract, + indexPatternId: string +): Promise> { + const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject; + + if (savedObject.error) { + throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`); + } + + return savedObject.attributes.title; +} diff --git a/src/plugins/data/public/index_patterns/lib/index.ts b/src/plugins/data/public/index_patterns/lib/index.ts index d1c229513aa339..c878eb91154277 100644 --- a/src/plugins/data/public/index_patterns/lib/index.ts +++ b/src/plugins/data/public/index_patterns/lib/index.ts @@ -17,4 +17,8 @@ * under the License. */ -export { getIndexPatternTitle } from './get_index_pattern_title'; +export { getTitle } from './get_title'; +export * from './types'; +export { validateIndexPattern } from './validate_index_pattern'; +export { IndexPatternMissingIndices } from './errors'; +export { getFromSavedObject } from './get_from_saved_object'; diff --git a/src/plugins/data/public/index_patterns/lib/types.ts b/src/plugins/data/public/index_patterns/lib/types.ts new file mode 100644 index 00000000000000..5eb309a1e5a9ce --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/types.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const ILLEGAL_CHARACTERS_KEY = 'ILLEGAL_CHARACTERS'; +export const CONTAINS_SPACES_KEY = 'CONTAINS_SPACES'; +export const ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|']; +export const ILLEGAL_CHARACTERS = ILLEGAL_CHARACTERS_VISIBLE.concat(' '); diff --git a/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts new file mode 100644 index 00000000000000..74e420ffeb5c05 --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.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 { CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY, ILLEGAL_CHARACTERS_VISIBLE } from './types'; + +import { validateIndexPattern } from './validate_index_pattern'; + +describe('Index Pattern Utils', () => { + describe('Validation', () => { + it('should not allow space in the pattern', () => { + const errors = validateIndexPattern('my pattern'); + expect(errors[CONTAINS_SPACES_KEY]).toBe(true); + }); + + it('should not allow illegal characters', () => { + ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { + const errors = validateIndexPattern(`pattern${char}`); + expect(errors[ILLEGAL_CHARACTERS_KEY]).toEqual([char]); + }); + }); + + it('should return empty object when there are no errors', () => { + expect(validateIndexPattern('my-pattern-*')).toEqual({}); + }); + }); +}); diff --git a/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts new file mode 100644 index 00000000000000..70f5971c91bd5d --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ILLEGAL_CHARACTERS_VISIBLE, CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY } from './types'; + +function indexPatternContainsSpaces(indexPattern: string): boolean { + return indexPattern.includes(' '); +} + +function findIllegalCharacters(indexPattern: string): string[] { + const illegalCharacters = ILLEGAL_CHARACTERS_VISIBLE.reduce((chars: string[], char: string) => { + if (indexPattern.includes(char)) { + chars.push(char); + } + return chars; + }, []); + + return illegalCharacters; +} + +export function validateIndexPattern(indexPattern: string) { + const errors: Record = {}; + + const illegalCharacters = findIllegalCharacters(indexPattern); + + if (illegalCharacters.length) { + errors[ILLEGAL_CHARACTERS_KEY] = illegalCharacters; + } + + if (indexPatternContainsSpaces(indexPattern)) { + errors[CONTAINS_SPACES_KEY] = true; + } + + return errors; +} diff --git a/src/plugins/data/public/index_patterns/utils.ts b/src/plugins/data/public/index_patterns/utils.ts new file mode 100644 index 00000000000000..0ecc87f3080fd9 --- /dev/null +++ b/src/plugins/data/public/index_patterns/utils.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 { find } from 'lodash'; +import { SavedObjectsClientContract, SimpleSavedObject } from 'src/core/public'; + +/** + * Returns an object matching a given title + * + * @param client {SavedObjectsClientContract} + * @param title {string} + * @returns {Promise} + */ +export async function findByTitle( + client: SavedObjectsClientContract, + title: string +): Promise | void> { + if (!title) { + return Promise.resolve(); + } + + const { savedObjects } = await client.find({ + type: 'index-pattern', + perPage: 10, + search: `"${title}"`, + searchFields: ['title'], + fields: ['title'], + }); + + return find( + savedObjects, + (obj: SimpleSavedObject) => obj.get('title').toLowerCase() === title.toLowerCase() + ); +} + +export function getRoutes() { + return { + edit: '/management/kibana/index_patterns/{{id}}', + addField: '/management/kibana/index_patterns/{{id}}/create-field', + indexedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:indexedFields)', + scriptedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:scriptedFields)', + sourceFilters: '/management/kibana/index_patterns/{{id}}?_a=(tab:sourceFilters)', + }; +} diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index ceb57b4a3a564d..058e6c0e2f5c52 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,7 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { FieldFormatRegisty, Plugin, FieldFormatsStart, FieldFormatsSetup } from '.'; +import { + FieldFormatRegisty, + Plugin, + FieldFormatsStart, + FieldFormatsSetup, + IndexPatternsContract, +} from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -69,6 +75,7 @@ const createStartContract = (): Start => { ui: { IndexPatternSelect: jest.fn(), }, + indexPatterns: {} as IndexPatternsContract, }; return startContract; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index d8c45b6786c0cf..2a37be7f3f46a1 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -19,13 +19,22 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { Storage } from '../../kibana_utils/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from './types'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, + DataSetupDependencies, + DataStartDependencies, +} from './types'; import { AutocompleteProviderRegister } from './autocomplete_provider'; import { getSuggestionsProvider } from './suggestions_provider'; import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; +import { IndexPatterns } from './index_patterns'; +import { setNotifications, setFieldFormats, setOverlays, setIndexPatterns } from './services'; +import { createFilterAction, GLOBAL_APPLY_FILTER_ACTION } from './actions'; +import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; export class DataPublicPlugin implements Plugin { private readonly autocomplete = new AutocompleteProviderRegister(); @@ -39,30 +48,47 @@ export class DataPublicPlugin implements Plugin = new Subject(); private fetch$: Subject = new Subject(); - private uiSettings: UiSettingsClientContract; + private uiSettings: IUiSettingsClient; - constructor(uiSettings: UiSettingsClientContract) { + constructor(uiSettings: IUiSettingsClient) { this.uiSettings = uiSettings; } diff --git a/src/plugins/data/public/query/index.tsx b/src/plugins/data/public/query/index.tsx index 224c2f3a04076b..dd3d11b4ac25c0 100644 --- a/src/plugins/data/public/query/index.tsx +++ b/src/plugins/data/public/query/index.tsx @@ -21,7 +21,6 @@ export * from './lib'; export * from './query_service'; export * from './filter_manager'; - export * from './timefilter'; - +export * from './saved_query'; export * from './persisted_log'; diff --git a/src/plugins/data/public/query/lib/get_query_log.ts b/src/plugins/data/public/query/lib/get_query_log.ts index 67073a9078046c..a71eb7580cf07e 100644 --- a/src/plugins/data/public/query/lib/get_query_log.ts +++ b/src/plugins/data/public/query/lib/get_query_log.ts @@ -17,12 +17,12 @@ * under the License. */ -import { UiSettingsClientContract } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { PersistedLog } from '../persisted_log'; export function getQueryLog( - uiSettings: UiSettingsClientContract, + uiSettings: IUiSettingsClient, storage: IStorageWrapper, appName: string, language: string diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index f2832b6b67fa2a..2710dadaa23a33 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -35,6 +35,7 @@ const createStartContractMock = () => { const startContract = { filterManager: jest.fn() as any, timefilter: timefilterServiceMock.createStartContract(), + savedQueries: jest.fn() as any, }; return startContract; diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index 206f8ac284ec37..ebef8b8d450500 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -17,10 +17,11 @@ * under the License. */ -import { UiSettingsClientContract } from 'src/core/public'; +import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { FilterManager } from './filter_manager'; import { TimefilterService, TimefilterSetup } from './timefilter'; +import { createSavedQueryService } from './saved_query/saved_query_service'; /** * Query Service @@ -29,9 +30,8 @@ import { TimefilterService, TimefilterSetup } from './timefilter'; export interface QueryServiceDependencies { storage: IStorageWrapper; - uiSettings: UiSettingsClientContract; + uiSettings: CoreStart['uiSettings']; } - export class QueryService { filterManager!: FilterManager; timefilter!: TimefilterSetup; @@ -51,10 +51,11 @@ export class QueryService { }; } - public start() { + public start(savedObjects: CoreStart['savedObjects']) { return { filterManager: this.filterManager, timefilter: this.timefilter, + savedQueries: createSavedQueryService(savedObjects.client), }; } diff --git a/src/plugins/data/public/query/saved_query/index.ts b/src/plugins/data/public/query/saved_query/index.ts new file mode 100644 index 00000000000000..f9b58e137b2768 --- /dev/null +++ b/src/plugins/data/public/query/saved_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. + */ + +export { SavedQuery, SavedQueryAttributes, SavedQueryService, SavedQueryTimeFilter } from './types'; +export { createSavedQueryService } from './saved_query_service'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts similarity index 98% rename from src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts rename to src/plugins/data/public/query/saved_query/saved_query_service.test.ts index 415da8a2c32cc1..ecb3fc2d606ec7 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SavedQueryAttributes } from '../index'; import { createSavedQueryService } from './saved_query_service'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { esFilters, SavedQueryAttributes } from '../..'; const savedQueryAttributes: SavedQueryAttributes = { title: 'foo', diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts similarity index 88% rename from src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts rename to src/plugins/data/public/query/saved_query/saved_query_service.ts index 2668ce911c3718..434efe80ecd8cd 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/public'; -import { SavedQueryAttributes, SavedQuery } from '../index'; +import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/public'; +import { SavedQueryAttributes, SavedQuery, SavedQueryService } from './types'; type SerializedSavedQueryAttributes = SavedObjectAttributes & SavedQueryAttributes & { @@ -29,22 +28,6 @@ type SerializedSavedQueryAttributes = SavedObjectAttributes & }; }; -export interface SavedQueryService { - saveQuery: ( - attributes: SavedQueryAttributes, - config?: { overwrite: boolean } - ) => Promise; - getAllSavedQueries: () => Promise; - findSavedQueries: ( - searchText?: string, - perPage?: number, - activePage?: number - ) => Promise; - getSavedQuery: (id: string) => Promise; - deleteSavedQuery: (id: string) => Promise<{}>; - getSavedQueryCount: () => Promise; -} - export const createSavedQueryService = ( savedObjectsClient: SavedObjectsClientContract ): SavedQueryService => { diff --git a/src/plugins/data/public/query/saved_query/types.ts b/src/plugins/data/public/query/saved_query/types.ts new file mode 100644 index 00000000000000..c278c2476c2e72 --- /dev/null +++ b/src/plugins/data/public/query/saved_query/types.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 { RefreshInterval, TimeRange, Query, esFilters } from '../..'; + +export type SavedQueryTimeFilter = TimeRange & { + refreshInterval: RefreshInterval; +}; + +export interface SavedQuery { + id: string; + attributes: SavedQueryAttributes; +} + +export interface SavedQueryAttributes { + title: string; + description: string; + query: Query; + filters?: esFilters.Filter[]; + timefilter?: SavedQueryTimeFilter; +} + +export interface SavedQueryService { + saveQuery: ( + attributes: SavedQueryAttributes, + config?: { overwrite: boolean } + ) => Promise; + getAllSavedQueries: () => Promise; + findSavedQueries: ( + searchText?: string, + perPage?: number, + activePage?: number + ) => Promise; + getSavedQuery: (id: string) => Promise; + deleteSavedQuery: (id: string) => Promise<{}>; + getSavedQueryCount: () => Promise; +} diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.ts b/src/plugins/data/public/query/timefilter/timefilter_service.ts index 831ccebedc9cc3..413163ed059ad4 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiSettingsClientContract } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from './index'; @@ -27,7 +27,7 @@ import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from */ export interface TimeFilterServiceDependencies { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; storage: IStorageWrapper; } diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts new file mode 100644 index 00000000000000..4a832105b69918 --- /dev/null +++ b/src/plugins/data/public/services.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 { NotificationsStart } from 'src/core/public'; +import { CoreStart } from 'kibana/public'; +import { FieldFormatsStart } from '.'; +import { createGetterSetter } from '../../kibana_utils/public'; +import { IndexPatternsContract } from './index_patterns'; + +export const [getNotifications, setNotifications] = createGetterSetter( + 'Notifications' +); + +export const [getFieldFormats, setFieldFormats] = createGetterSetter( + 'FieldFormats' +); + +export const [getOverlays, setOverlays] = createGetterSetter('Overlays'); + +export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( + 'IndexPatterns' +); diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts index 7dc8ff0fe133d1..9089105b4e3a82 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts +++ b/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts @@ -17,12 +17,9 @@ * under the License. */ -// TODO: remove when index patterns are moved here. -jest.mock('ui/new_platform'); - import { stubIndexPattern, stubFields } from '../stubs'; import { getSuggestionsProvider } from './value_suggestions'; -import { UiSettingsClientContract } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; describe('getSuggestions', () => { let getSuggestions: any; @@ -30,7 +27,7 @@ describe('getSuggestions', () => { describe('with value suggestions disabled', () => { beforeEach(() => { - const config = { get: (key: string) => false } as UiSettingsClientContract; + const config = { get: (key: string) => false } as IUiSettingsClient; http = { fetch: jest.fn() }; getSuggestions = getSuggestionsProvider(config, http); }); @@ -47,7 +44,7 @@ describe('getSuggestions', () => { describe('with value suggestions enabled', () => { beforeEach(() => { - const config = { get: (key: string) => true } as UiSettingsClientContract; + const config = { get: (key: string) => true } as IUiSettingsClient; http = { fetch: jest.fn() }; getSuggestions = getSuggestionsProvider(config, http); }); diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.ts index 3bc1b45d873956..68076cd43c336b 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/suggestions_provider/value_suggestions.ts @@ -19,32 +19,45 @@ import { memoize } from 'lodash'; -import { UiSettingsClientContract, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, HttpServiceBase } from 'src/core/public'; import { IGetSuggestions } from './types'; import { IFieldType } from '../../common'; export function getSuggestionsProvider( - uiSettings: UiSettingsClientContract, + uiSettings: IUiSettingsClient, http: HttpServiceBase ): IGetSuggestions { const requestSuggestions = memoize( - (index: string, field: IFieldType, query: string, boolFilter: any = []) => { + ( + index: string, + field: IFieldType, + query: string, + boolFilter: any = [], + signal?: AbortSignal + ) => { return http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, field: field.name, boolFilter }), + signal, }); }, resolver ); - return async (index: string, field: IFieldType, query: string, boolFilter?: any) => { + return async ( + index: string, + field: IFieldType, + query: string, + boolFilter?: any, + signal?: AbortSignal + ) => { const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues'); if (field.type === 'boolean') { return [true, false]; } else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') { return []; } - return await requestSuggestions(index, field, query, boolFilter); + return await requestSuggestions(index, field, query, boolFilter, signal); }; } diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index c0c96372f9f59b..202a509ee58c95 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -19,12 +19,22 @@ import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { IUiActionsSetup, IUiActionsStart } from 'src/plugins/ui_actions/public'; import { AutocompletePublicPluginSetup, AutocompletePublicPluginStart } from '.'; import { FieldFormatsSetup, FieldFormatsStart } from './field_formats_provider'; import { ISearchSetup, ISearchStart } from './search'; import { IGetSuggestions } from './suggestions_provider/types'; import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; +import { IndexPatternsContract } from './index_patterns'; + +export interface DataSetupDependencies { + uiActions: IUiActionsSetup; +} + +export interface DataStartDependencies { + uiActions: IUiActionsStart; +} export interface DataPublicPluginSetup { autocomplete: AutocompletePublicPluginSetup; @@ -36,6 +46,7 @@ export interface DataPublicPluginSetup { export interface DataPublicPluginStart { autocomplete: AutocompletePublicPluginStart; getSuggestions: IGetSuggestions; + indexPatterns: IndexPatternsContract; search: ISearchStart; fieldFormats: FieldFormatsStart; query: QueryStart; diff --git a/src/plugins/data/public/ui/_index.scss b/src/plugins/data/public/ui/_index.scss new file mode 100644 index 00000000000000..39f29ac7775880 --- /dev/null +++ b/src/plugins/data/public/ui/_index.scss @@ -0,0 +1,8 @@ + +@import './filter_bar/index'; + +@import './typeahead/index'; + +@import './saved_query_management/index'; + +@import './query_string_input/index'; diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss index 84538a62ca005f..51204e2a611688 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss @@ -1,6 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; -@import '@elastic/eui/src/components/form/mixins'; - /** * 1. Allow wrapping of long filter items */ diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts index 2cc7f16cfe261b..fb3fbc10d74552 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_editor_utils.test.ts @@ -36,8 +36,6 @@ import { import { existsOperator, isBetweenOperator, isOneOfOperator, isOperator } from './filter_operators'; -jest.mock('ui/new_platform'); - describe('Filter editor utils', () => { describe('getFieldFromFilter', () => { it('should return the field from the filter', () => { diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 4ef0b2740e5fad..1921f6672755db 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -21,7 +21,7 @@ import { EuiContextMenu, EuiPopover } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { Component } from 'react'; -import { UiSettingsClientContract } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; import { esFilters, utils, IIndexPattern } from '../..'; @@ -34,7 +34,7 @@ interface Props { onUpdate: (filter: esFilters.Filter) => void; onRemove: () => void; intl: InjectedIntl; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } interface State { diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 607f690d41c67b..8bfccd49bdff34 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -20,4 +20,9 @@ export { SuggestionsComponent } from './typeahead/suggestions_component'; export { IndexPatternSelect } from './index_pattern_select'; export { FilterBar } from './filter_bar'; -export { applyFiltersPopover } from './apply_filters'; +export { QueryStringInput } from './query_string_input/query_string_input'; + +// temp export - will be removed as final components are migrated to NP +export { QueryBarTopRow } from './query_string_input/query_bar_top_row'; +export { SavedQueryManagementComponent } from './saved_query_management'; +export { SaveQueryForm, SavedQueryMeta } from './saved_query_form'; diff --git a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index f868e4b1f7504c..ad453c4e5d11d2 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -22,7 +22,7 @@ import React, { Component } from 'react'; import { EuiComboBox } from '@elastic/eui'; import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; -import { getIndexPatternTitle } from '../../index_patterns/lib'; +import { getTitle } from '../../index_patterns/lib'; export interface IndexPatternSelectProps { onChange: (opt: any) => void; @@ -88,7 +88,7 @@ export class IndexPatternSelect extends Component { this.fetchSelectedIndexPattern(this.props.indexPatternId); } - componentWillReceiveProps(nextProps: IndexPatternSelectProps) { + UNSAFE_componentWillReceiveProps(nextProps: IndexPatternSelectProps) { if (nextProps.indexPatternId !== this.props.indexPatternId) { this.fetchSelectedIndexPattern(nextProps.indexPatternId); } @@ -104,7 +104,7 @@ export class IndexPatternSelect extends Component { let indexPatternTitle; try { - indexPatternTitle = await getIndexPatternTitle(this.props.savedObjectsClient, indexPatternId); + indexPatternTitle = await getTitle(this.props.savedObjectsClient, indexPatternId); } catch (err) { // index pattern no longer exists return; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap similarity index 99% rename from src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap rename to src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap index d4990ed59f441a..7ab7d7653eb5e3 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap @@ -206,7 +206,6 @@ exports[`LanguageSwitcher should toggle off if language is lucene 1`] = ` "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, } } @@ -497,7 +496,6 @@ exports[`LanguageSwitcher should toggle on if language is kuery 1`] = ` "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, } } diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap similarity index 89% rename from src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap rename to src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 6f155de95d6ebb..80a5ede5670543 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -145,9 +145,72 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "setBrand": [MockFunction], "setBreadcrumbs": [MockFunction], "setHelpExtension": [MockFunction], + "setHelpSupportUrl": [MockFunction], "setIsCollapsed": [MockFunction], "setIsVisible": [MockFunction], }, + "data": Object { + "autocomplete": Object { + "addProvider": [MockFunction], + "clearProviders": [MockFunction], + "getProvider": [MockFunction], + }, + "fieldFormats": Object { + "getByFieldType": [MockFunction], + "getConfig": [MockFunction], + "getDefaultConfig": [MockFunction], + "getDefaultInstance": [MockFunction], + "getDefaultInstanceCacheResolver": [MockFunction], + "getDefaultInstancePlain": [MockFunction], + "getDefaultType": [MockFunction], + "getDefaultTypeName": [MockFunction], + "getInstance": [MockFunction], + "getType": [MockFunction], + "getTypeNameByEsTypes": [MockFunction], + "init": [MockFunction], + "parseDefaultTypeMap": [MockFunction], + "register": [MockFunction], + }, + "getSuggestions": [MockFunction], + "indexPatterns": Object {}, + "query": Object { + "filterManager": [MockFunction], + "savedQueries": [MockFunction], + "timefilter": Object { + "history": Object { + "add": [MockFunction], + "get": [MockFunction], + }, + "timefilter": Object { + "calculateBounds": [MockFunction], + "createFilter": [MockFunction], + "disableAutoRefreshSelector": [MockFunction], + "disableTimeRangeSelector": [MockFunction], + "enableAutoRefreshSelector": [MockFunction], + "enableTimeRangeSelector": [MockFunction], + "getActiveBounds": [MockFunction], + "getAutoRefreshFetch$": [MockFunction], + "getBounds": [MockFunction], + "getEnabledUpdated$": [MockFunction], + "getFetch$": [MockFunction], + "getRefreshInterval": [MockFunction], + "getRefreshIntervalUpdate$": [MockFunction], + "getTime": [MockFunction], + "getTimeUpdate$": [MockFunction], + "isAutoRefreshSelectorEnabled": [MockFunction], + "isTimeRangeSelectorEnabled": [MockFunction], + "setRefreshInterval": [MockFunction], + "setTime": [MockFunction], + }, + }, + }, + "search": Object { + "search": [MockFunction], + }, + "ui": Object { + "IndexPatternSelect": [MockFunction], + }, + }, "docLinks": Object { "DOC_LINK_VERSION": "mocked-test-branch", "ELASTIC_WEBSITE_URL": "https://www.elastic.co/", @@ -236,14 +299,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -344,7 +402,6 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, } } @@ -708,9 +765,72 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "setBrand": [MockFunction], "setBreadcrumbs": [MockFunction], "setHelpExtension": [MockFunction], + "setHelpSupportUrl": [MockFunction], "setIsCollapsed": [MockFunction], "setIsVisible": [MockFunction], }, + "data": Object { + "autocomplete": Object { + "addProvider": [MockFunction], + "clearProviders": [MockFunction], + "getProvider": [MockFunction], + }, + "fieldFormats": Object { + "getByFieldType": [MockFunction], + "getConfig": [MockFunction], + "getDefaultConfig": [MockFunction], + "getDefaultInstance": [MockFunction], + "getDefaultInstanceCacheResolver": [MockFunction], + "getDefaultInstancePlain": [MockFunction], + "getDefaultType": [MockFunction], + "getDefaultTypeName": [MockFunction], + "getInstance": [MockFunction], + "getType": [MockFunction], + "getTypeNameByEsTypes": [MockFunction], + "init": [MockFunction], + "parseDefaultTypeMap": [MockFunction], + "register": [MockFunction], + }, + "getSuggestions": [MockFunction], + "indexPatterns": Object {}, + "query": Object { + "filterManager": [MockFunction], + "savedQueries": [MockFunction], + "timefilter": Object { + "history": Object { + "add": [MockFunction], + "get": [MockFunction], + }, + "timefilter": Object { + "calculateBounds": [MockFunction], + "createFilter": [MockFunction], + "disableAutoRefreshSelector": [MockFunction], + "disableTimeRangeSelector": [MockFunction], + "enableAutoRefreshSelector": [MockFunction], + "enableTimeRangeSelector": [MockFunction], + "getActiveBounds": [MockFunction], + "getAutoRefreshFetch$": [MockFunction], + "getBounds": [MockFunction], + "getEnabledUpdated$": [MockFunction], + "getFetch$": [MockFunction], + "getRefreshInterval": [MockFunction], + "getRefreshIntervalUpdate$": [MockFunction], + "getTime": [MockFunction], + "getTimeUpdate$": [MockFunction], + "isAutoRefreshSelectorEnabled": [MockFunction], + "isTimeRangeSelectorEnabled": [MockFunction], + "setRefreshInterval": [MockFunction], + "setTime": [MockFunction], + }, + }, + }, + "search": Object { + "search": [MockFunction], + }, + "ui": Object { + "IndexPatternSelect": [MockFunction], + }, + }, "docLinks": Object { "DOC_LINK_VERSION": "mocked-test-branch", "ELASTIC_WEBSITE_URL": "https://www.elastic.co/", @@ -799,14 +919,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -907,7 +1022,6 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, }, } @@ -1259,9 +1373,72 @@ exports[`QueryStringInput Should pass the query language to the language switche "setBrand": [MockFunction], "setBreadcrumbs": [MockFunction], "setHelpExtension": [MockFunction], + "setHelpSupportUrl": [MockFunction], "setIsCollapsed": [MockFunction], "setIsVisible": [MockFunction], }, + "data": Object { + "autocomplete": Object { + "addProvider": [MockFunction], + "clearProviders": [MockFunction], + "getProvider": [MockFunction], + }, + "fieldFormats": Object { + "getByFieldType": [MockFunction], + "getConfig": [MockFunction], + "getDefaultConfig": [MockFunction], + "getDefaultInstance": [MockFunction], + "getDefaultInstanceCacheResolver": [MockFunction], + "getDefaultInstancePlain": [MockFunction], + "getDefaultType": [MockFunction], + "getDefaultTypeName": [MockFunction], + "getInstance": [MockFunction], + "getType": [MockFunction], + "getTypeNameByEsTypes": [MockFunction], + "init": [MockFunction], + "parseDefaultTypeMap": [MockFunction], + "register": [MockFunction], + }, + "getSuggestions": [MockFunction], + "indexPatterns": Object {}, + "query": Object { + "filterManager": [MockFunction], + "savedQueries": [MockFunction], + "timefilter": Object { + "history": Object { + "add": [MockFunction], + "get": [MockFunction], + }, + "timefilter": Object { + "calculateBounds": [MockFunction], + "createFilter": [MockFunction], + "disableAutoRefreshSelector": [MockFunction], + "disableTimeRangeSelector": [MockFunction], + "enableAutoRefreshSelector": [MockFunction], + "enableTimeRangeSelector": [MockFunction], + "getActiveBounds": [MockFunction], + "getAutoRefreshFetch$": [MockFunction], + "getBounds": [MockFunction], + "getEnabledUpdated$": [MockFunction], + "getFetch$": [MockFunction], + "getRefreshInterval": [MockFunction], + "getRefreshIntervalUpdate$": [MockFunction], + "getTime": [MockFunction], + "getTimeUpdate$": [MockFunction], + "isAutoRefreshSelectorEnabled": [MockFunction], + "isTimeRangeSelectorEnabled": [MockFunction], + "setRefreshInterval": [MockFunction], + "setTime": [MockFunction], + }, + }, + }, + "search": Object { + "search": [MockFunction], + }, + "ui": Object { + "IndexPatternSelect": [MockFunction], + }, + }, "docLinks": Object { "DOC_LINK_VERSION": "mocked-test-branch", "ELASTIC_WEBSITE_URL": "https://www.elastic.co/", @@ -1350,14 +1527,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -1458,7 +1630,6 @@ exports[`QueryStringInput Should pass the query language to the language switche "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, } } @@ -1819,9 +1990,72 @@ exports[`QueryStringInput Should pass the query language to the language switche "setBrand": [MockFunction], "setBreadcrumbs": [MockFunction], "setHelpExtension": [MockFunction], + "setHelpSupportUrl": [MockFunction], "setIsCollapsed": [MockFunction], "setIsVisible": [MockFunction], }, + "data": Object { + "autocomplete": Object { + "addProvider": [MockFunction], + "clearProviders": [MockFunction], + "getProvider": [MockFunction], + }, + "fieldFormats": Object { + "getByFieldType": [MockFunction], + "getConfig": [MockFunction], + "getDefaultConfig": [MockFunction], + "getDefaultInstance": [MockFunction], + "getDefaultInstanceCacheResolver": [MockFunction], + "getDefaultInstancePlain": [MockFunction], + "getDefaultType": [MockFunction], + "getDefaultTypeName": [MockFunction], + "getInstance": [MockFunction], + "getType": [MockFunction], + "getTypeNameByEsTypes": [MockFunction], + "init": [MockFunction], + "parseDefaultTypeMap": [MockFunction], + "register": [MockFunction], + }, + "getSuggestions": [MockFunction], + "indexPatterns": Object {}, + "query": Object { + "filterManager": [MockFunction], + "savedQueries": [MockFunction], + "timefilter": Object { + "history": Object { + "add": [MockFunction], + "get": [MockFunction], + }, + "timefilter": Object { + "calculateBounds": [MockFunction], + "createFilter": [MockFunction], + "disableAutoRefreshSelector": [MockFunction], + "disableTimeRangeSelector": [MockFunction], + "enableAutoRefreshSelector": [MockFunction], + "enableTimeRangeSelector": [MockFunction], + "getActiveBounds": [MockFunction], + "getAutoRefreshFetch$": [MockFunction], + "getBounds": [MockFunction], + "getEnabledUpdated$": [MockFunction], + "getFetch$": [MockFunction], + "getRefreshInterval": [MockFunction], + "getRefreshIntervalUpdate$": [MockFunction], + "getTime": [MockFunction], + "getTimeUpdate$": [MockFunction], + "isAutoRefreshSelectorEnabled": [MockFunction], + "isTimeRangeSelectorEnabled": [MockFunction], + "setRefreshInterval": [MockFunction], + "setTime": [MockFunction], + }, + }, + }, + "search": Object { + "search": [MockFunction], + }, + "ui": Object { + "IndexPatternSelect": [MockFunction], + }, + }, "docLinks": Object { "DOC_LINK_VERSION": "mocked-test-branch", "ELASTIC_WEBSITE_URL": "https://www.elastic.co/", @@ -1910,14 +2144,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -2018,7 +2247,6 @@ exports[`QueryStringInput Should pass the query language to the language switche "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, }, } @@ -2370,9 +2598,72 @@ exports[`QueryStringInput Should render the given query 1`] = ` "setBrand": [MockFunction], "setBreadcrumbs": [MockFunction], "setHelpExtension": [MockFunction], + "setHelpSupportUrl": [MockFunction], "setIsCollapsed": [MockFunction], "setIsVisible": [MockFunction], }, + "data": Object { + "autocomplete": Object { + "addProvider": [MockFunction], + "clearProviders": [MockFunction], + "getProvider": [MockFunction], + }, + "fieldFormats": Object { + "getByFieldType": [MockFunction], + "getConfig": [MockFunction], + "getDefaultConfig": [MockFunction], + "getDefaultInstance": [MockFunction], + "getDefaultInstanceCacheResolver": [MockFunction], + "getDefaultInstancePlain": [MockFunction], + "getDefaultType": [MockFunction], + "getDefaultTypeName": [MockFunction], + "getInstance": [MockFunction], + "getType": [MockFunction], + "getTypeNameByEsTypes": [MockFunction], + "init": [MockFunction], + "parseDefaultTypeMap": [MockFunction], + "register": [MockFunction], + }, + "getSuggestions": [MockFunction], + "indexPatterns": Object {}, + "query": Object { + "filterManager": [MockFunction], + "savedQueries": [MockFunction], + "timefilter": Object { + "history": Object { + "add": [MockFunction], + "get": [MockFunction], + }, + "timefilter": Object { + "calculateBounds": [MockFunction], + "createFilter": [MockFunction], + "disableAutoRefreshSelector": [MockFunction], + "disableTimeRangeSelector": [MockFunction], + "enableAutoRefreshSelector": [MockFunction], + "enableTimeRangeSelector": [MockFunction], + "getActiveBounds": [MockFunction], + "getAutoRefreshFetch$": [MockFunction], + "getBounds": [MockFunction], + "getEnabledUpdated$": [MockFunction], + "getFetch$": [MockFunction], + "getRefreshInterval": [MockFunction], + "getRefreshIntervalUpdate$": [MockFunction], + "getTime": [MockFunction], + "getTimeUpdate$": [MockFunction], + "isAutoRefreshSelectorEnabled": [MockFunction], + "isTimeRangeSelectorEnabled": [MockFunction], + "setRefreshInterval": [MockFunction], + "setTime": [MockFunction], + }, + }, + }, + "search": Object { + "search": [MockFunction], + }, + "ui": Object { + "IndexPatternSelect": [MockFunction], + }, + }, "docLinks": Object { "DOC_LINK_VERSION": "mocked-test-branch", "ELASTIC_WEBSITE_URL": "https://www.elastic.co/", @@ -2461,14 +2752,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -2569,7 +2855,6 @@ exports[`QueryStringInput Should render the given query 1`] = ` "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, } } @@ -2930,9 +3215,72 @@ exports[`QueryStringInput Should render the given query 1`] = ` "setBrand": [MockFunction], "setBreadcrumbs": [MockFunction], "setHelpExtension": [MockFunction], + "setHelpSupportUrl": [MockFunction], "setIsCollapsed": [MockFunction], "setIsVisible": [MockFunction], }, + "data": Object { + "autocomplete": Object { + "addProvider": [MockFunction], + "clearProviders": [MockFunction], + "getProvider": [MockFunction], + }, + "fieldFormats": Object { + "getByFieldType": [MockFunction], + "getConfig": [MockFunction], + "getDefaultConfig": [MockFunction], + "getDefaultInstance": [MockFunction], + "getDefaultInstanceCacheResolver": [MockFunction], + "getDefaultInstancePlain": [MockFunction], + "getDefaultType": [MockFunction], + "getDefaultTypeName": [MockFunction], + "getInstance": [MockFunction], + "getType": [MockFunction], + "getTypeNameByEsTypes": [MockFunction], + "init": [MockFunction], + "parseDefaultTypeMap": [MockFunction], + "register": [MockFunction], + }, + "getSuggestions": [MockFunction], + "indexPatterns": Object {}, + "query": Object { + "filterManager": [MockFunction], + "savedQueries": [MockFunction], + "timefilter": Object { + "history": Object { + "add": [MockFunction], + "get": [MockFunction], + }, + "timefilter": Object { + "calculateBounds": [MockFunction], + "createFilter": [MockFunction], + "disableAutoRefreshSelector": [MockFunction], + "disableTimeRangeSelector": [MockFunction], + "enableAutoRefreshSelector": [MockFunction], + "enableTimeRangeSelector": [MockFunction], + "getActiveBounds": [MockFunction], + "getAutoRefreshFetch$": [MockFunction], + "getBounds": [MockFunction], + "getEnabledUpdated$": [MockFunction], + "getFetch$": [MockFunction], + "getRefreshInterval": [MockFunction], + "getRefreshIntervalUpdate$": [MockFunction], + "getTime": [MockFunction], + "getTimeUpdate$": [MockFunction], + "isAutoRefreshSelectorEnabled": [MockFunction], + "isTimeRangeSelectorEnabled": [MockFunction], + "setRefreshInterval": [MockFunction], + "setTime": [MockFunction], + }, + }, + }, + "search": Object { + "search": [MockFunction], + }, + "ui": Object { + "IndexPatternSelect": [MockFunction], + }, + }, "docLinks": Object { "DOC_LINK_VERSION": "mocked-test-branch", "ELASTIC_WEBSITE_URL": "https://www.elastic.co/", @@ -3021,14 +3369,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -3129,7 +3472,6 @@ exports[`QueryStringInput Should render the given query 1`] = ` "overrideLocalDefault": [MockFunction], "remove": [MockFunction], "set": [MockFunction], - "stop": [MockFunction], }, }, } diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/_index.scss b/src/plugins/data/public/ui/query_string_input/_index.scss similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/_index.scss rename to src/plugins/data/public/ui/query_string_input/_index.scss diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/_query_bar.scss rename to src/plugins/data/public/ui/query_string_input/_query_bar.scss diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts b/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts similarity index 88% rename from src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts rename to src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts index 4cf17dc9be37ef..6bef11e4fc46cf 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts +++ b/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts @@ -17,13 +17,13 @@ * under the License. */ import { isEmpty } from 'lodash'; -import { UiSettingsClientContract, SavedObjectsClientContract } from 'src/core/public'; -import { getFromSavedObject } from '../../../index_patterns'; +import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/public'; +import { indexPatterns } from '../..'; export async function fetchIndexPatterns( savedObjectsClient: SavedObjectsClientContract, indexPatternStrings: string[], - uiSettings: UiSettingsClientContract + uiSettings: IUiSettingsClient ) { if (!indexPatternStrings || isEmpty(indexPatternStrings)) { return []; @@ -48,5 +48,5 @@ export async function fetchIndexPatterns( ? exactMatches : [...exactMatches, await savedObjectsClient.get('index-pattern', defaultIndex)]; - return allMatches.map(getFromSavedObject); + return allMatches.map(indexPatterns.getFromSavedObject); } diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx similarity index 96% rename from src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx rename to src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx index ab210071870ca4..e3ec5212abfd2c 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { QueryLanguageSwitcher } from './language_switcher'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; const startMock = coreMock.createStart(); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx similarity index 98% rename from src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx rename to src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 31b0e375eaac69..d86a8a970a8e70 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -31,7 +31,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; -import { useKibana } from '../../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../kibana_react/public'; interface Props { language: string; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx similarity index 95% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx rename to src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx index ea01347e388654..70d0c96b4733f3 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx @@ -23,14 +23,11 @@ import React from 'react'; import { mount } from 'enzyme'; import { QueryBarTopRow } from './query_bar_top_row'; -/* eslint-disable @kbn/eslint/no-restricted-paths */ - -import { stubIndexPatternWithFields } from '../../../../../../../plugins/data/public/stubs'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ - -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; +import { dataPluginMock } from '../../mocks'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; +import { stubIndexPatternWithFields } from '../../stubs'; const startMock = coreMock.createStart(); const mockTimeHistory = { @@ -99,6 +96,7 @@ function wrapQueryBarTopRowInContext(testProps: any) { const services = { ...startMock, + data: dataPluginMock.createStartContract(), appName: 'discover', storage: createMockStorage(), }; @@ -121,7 +119,7 @@ describe('QueryBarTopRowTopRow', () => { jest.clearAllMocks(); }); - it('Should render the given query', () => { + it('Should render query and time picker', () => { const component = mount( wrapQueryBarTopRowInContext({ query: kqlQuery, diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx similarity index 98% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx rename to src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 824e8cf1e2a7c0..aa32ebc9a56cc0 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -41,8 +41,8 @@ import { PersistedLog, getQueryLog, esKuery, -} from '../../../../../../../plugins/data/public'; -import { useKibana, toMountPoint } from '../../../../../../../plugins/kibana_react/public'; +} from '../..'; +import { useKibana, toMountPoint } from '../../../../kibana_react/public'; import { QueryStringInput } from './query_string_input'; interface Props { diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.mocks.ts b/src/plugins/data/public/ui/query_string_input/query_string_input.test.mocks.ts similarity index 84% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.mocks.ts rename to src/plugins/data/public/ui/query_string_input/query_string_input.test.mocks.ts index 0c8d7996532956..12222ee7ad2671 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.mocks.ts +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.mocks.ts @@ -16,11 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -/* eslint-disable @kbn/eslint/no-restricted-paths */ - -import { stubIndexPatternWithFields } from '../../../../../../../plugins/data/public/stubs'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ +import { stubIndexPatternWithFields } from '../../stubs'; export const mockPersistedLog = { add: jest.fn(), @@ -35,7 +31,7 @@ export const mockFetchIndexPatterns = jest .fn() .mockReturnValue(Promise.resolve([stubIndexPatternWithFields])); -jest.mock('../../../../../../../plugins/data/public/query/persisted_log', () => ({ +jest.mock('../../query/persisted_log', () => ({ PersistedLog: mockPersistedLogFactory, })); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx similarity index 96% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.tsx rename to src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx index 3512604b362611..4435bd87cd2d71 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx @@ -27,12 +27,10 @@ import { EuiFieldText } from '@elastic/eui'; import React from 'react'; import { QueryLanguageSwitcher } from './language_switcher'; import { QueryStringInput, QueryStringInputUI } from './query_string_input'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; +import { dataPluginMock } from '../../mocks'; const startMock = coreMock.createStart(); -/* eslint-disable @kbn/eslint/no-restricted-paths */ - -import { stubIndexPatternWithFields } from '../../../../../../../plugins/data/public/stubs'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ +import { stubIndexPatternWithFields } from '../../stubs'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; @@ -77,6 +75,7 @@ function wrapQueryStringInputInContext(testProps: any, storage?: any) { const services = { ...startMock, + data: dataPluginMock.createStartContract(), appName: testProps.appName || 'test', storage: storage || createMockStorage(), }; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx similarity index 95% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.tsx rename to src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 37519551ac5ad6..16b22a164f2f02 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -46,14 +46,10 @@ import { matchPairs, getQueryLog, Query, -} from '../../../../../../../plugins/data/public'; -import { - withKibana, - KibanaReactContextValue, - toMountPoint, -} from '../../../../../../../plugins/kibana_react/public'; -import { QueryLanguageSwitcher } from './language_switcher'; +} from '../..'; +import { withKibana, KibanaReactContextValue, toMountPoint } from '../../../../kibana_react/public'; import { fetchIndexPatterns } from './fetch_index_patterns'; +import { QueryLanguageSwitcher } from './language_switcher'; interface Props { kibana: KibanaReactContextValue; @@ -110,6 +106,7 @@ export class QueryStringInputUI extends Component { public inputRef: HTMLInputElement | null = null; private persistedLog: PersistedLog | undefined; + private abortController: AbortController | undefined; private services = this.props.kibana.services; private componentIsUnmounting = false; @@ -167,12 +164,22 @@ export class QueryStringInputUI extends Component { return; } - const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ - query: queryString, - selectionStart, - selectionEnd, - }); - return [...suggestions, ...recentSearchSuggestions]; + try { + if (this.abortController) this.abortController.abort(); + this.abortController = new AbortController(); + const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ + query: queryString, + selectionStart, + selectionEnd, + signal: this.abortController.signal, + }); + return [...suggestions, ...recentSearchSuggestions]; + } catch (e) { + // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error + // Ignore aborted requests + if (e.message === 'The user aborted a request.') return; + throw e; + } }; private getRecentSearchSuggestions = (query: string) => { diff --git a/src/plugins/data/public/ui/saved_query_form/index.ts b/src/plugins/data/public/ui/saved_query_form/index.ts new file mode 100644 index 00000000000000..c52b6c92ef6d3e --- /dev/null +++ b/src/plugins/data/public/ui/saved_query_form/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 { SavedQueryMeta, SaveQueryForm } from '../saved_query_form/save_query_form'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx similarity index 98% rename from src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx rename to src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 7a9786f5f9ce82..f9a0ae4e803c44 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -35,8 +35,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { sortBy, isEqual } from 'lodash'; -import { SavedQuery, SavedQueryAttributes } from '../../index'; -import { SavedQueryService } from '../../lib/saved_query_service'; +import { SavedQuery, SavedQueryAttributes, SavedQueryService } from '../..'; interface Props { savedQuery?: SavedQueryAttributes; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_index.scss b/src/plugins/data/public/ui/saved_query_management/_index.scss similarity index 100% rename from src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_index.scss rename to src/plugins/data/public/ui/saved_query_management/_index.scss diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_saved_query_list_item.scss b/src/plugins/data/public/ui/saved_query_management/_saved_query_list_item.scss similarity index 100% rename from src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_saved_query_list_item.scss rename to src/plugins/data/public/ui/saved_query_management/_saved_query_list_item.scss diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_saved_query_management_component.scss b/src/plugins/data/public/ui/saved_query_management/_saved_query_management_component.scss similarity index 93% rename from src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_saved_query_management_component.scss rename to src/plugins/data/public/ui/saved_query_management/_saved_query_management_component.scss index d43d14945699e0..928cb5a34d6deb 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/_saved_query_management_component.scss +++ b/src/plugins/data/public/ui/saved_query_management/_saved_query_management_component.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .kbnSavedQueryManagement__popover { max-width: $euiFormMaxWidth; } diff --git a/src/plugins/data/public/ui/saved_query_management/index.ts b/src/plugins/data/public/ui/saved_query_management/index.ts new file mode 100644 index 00000000000000..be5ea0cd7c8e72 --- /dev/null +++ b/src/plugins/data/public/ui/saved_query_management/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 { SavedQueryManagementComponent } from './saved_query_management_component'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx similarity index 99% rename from src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx rename to src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx index e8cf8a3d61ecd5..09eeb8359dde7e 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx @@ -22,7 +22,7 @@ import { EuiListGroupItem, EuiConfirmModal, EuiOverlayMask, EuiIconTip } from '@ import React, { Fragment, useState } from 'react'; import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; -import { SavedQuery } from '../../index'; +import { SavedQuery } from '../..'; interface Props { savedQuery: SavedQuery; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx similarity index 98% rename from src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx rename to src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index b73b8edb39e547..2a11531ee336df 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -35,8 +35,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { FunctionComponent, useEffect, useState, Fragment } from 'react'; import { sortBy } from 'lodash'; -import { SavedQuery } from '../../index'; -import { SavedQueryService } from '../../lib/saved_query_service'; +import { SavedQuery, SavedQueryService } from '../..'; import { SavedQueryListItem } from './saved_query_list_item'; const perPage = 50; diff --git a/src/plugins/data/public/ui/typeahead/_index.scss b/src/plugins/data/public/ui/typeahead/_index.scss index 8ff2965158ad9f..c0ba5f05f589c8 100644 --- a/src/plugins/data/public/ui/typeahead/_index.scss +++ b/src/plugins/data/public/ui/typeahead/_index.scss @@ -1 +1 @@ -@import 'suggestion'; \ No newline at end of file +@import 'suggestion'; diff --git a/src/plugins/data/public/ui/typeahead/_suggestion.scss b/src/plugins/data/public/ui/typeahead/_suggestion.scss index e1daf118d97065..3a215ceddcd00b 100644 --- a/src/plugins/data/public/ui/typeahead/_suggestion.scss +++ b/src/plugins/data/public/ui/typeahead/_suggestion.scss @@ -3,7 +3,7 @@ $kbnTypeaheadTypes: ( field: $euiColorWarning, value: $euiColorSecondary, operator: $euiColorPrimary, - conjunction: $typeaheadConjunctionColor, + conjunction: $euiColorVis3, ); .kbnTypeahead { diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index 88d2d873521cb0..d9a284d34de6be 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -144,6 +144,13 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } }); }); + it('returns nested sub-fields as non-aggregatable', () => { + const fields = readFieldCapsResponse(esResponse); + // Normally a keyword field would be aggregatable, but the fact that it is nested overrides that + const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); + expect(child).toHaveProperty('aggregatable', false); + }); + 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'); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 06eb30db0b24bb..2215bd8a95a1d0 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -182,6 +182,14 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie if (Object.keys(subType).length > 0) { field.subType = subType; + + // We don't support aggregating on nested fields, trying to do so in the UI will return + // blank results. For now we will stop showing nested fields as an option for aggregation. + // Once we add support for nested fields this condition should be removed and old index + // patterns should be migrated. + if (field.subType.nested) { + field.aggregatable = false; + } } } }); diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts index a9e0e33cbb9678..6b8f2223a14a56 100644 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts +++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts @@ -18,7 +18,7 @@ */ import { createFilterAction } from './apply_filter_action'; -import { expectError } from '../../tests/helpers'; +import { expectErrorAsync } from '../../tests/helpers'; test('has APPLY_FILTER_ACTION type and id', () => { const action = createFilterAction(); @@ -95,12 +95,13 @@ describe('execute()', () => { describe('when no filters are given', () => { test('throws an error', async () => { const action = createFilterAction(); - const error = expectError(() => + const error = await expectErrorAsync(() => action.execute({ embeddable: getEmbeddable(), } as any) ); expect(error).toBeInstanceOf(Error); + expect(error.message).toBe('Applying a filter requires a filter and embeddable as context'); }); test('updates filter input on success', async () => { diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index bce16747ed48e9..71e7cca3552bbf 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -240,6 +240,7 @@ export abstract class Container< ...this.input.panels, [panelState.explicitInput.id]: panelState, }, + isEmptyState: false, } as Partial); return await this.untilEmbeddableLoaded(panelState.explicitInput.id); diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 0b864e1a3573be..01975827789407 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -28,6 +28,11 @@ export interface EmbeddableInput { id: string; lastReloadRequestTime?: number; hidePanelTitles?: boolean; + isEmptyState?: boolean; + /** + * List of action IDs that this embeddable should not render. + */ + disabledActions?: string[]; } export interface EmbeddableOutput { diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 9eed400daf9c9b..196d6f934134bf 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { I18nProvider } from '@kbn/i18n/react'; import { CONTEXT_MENU_TRIGGER } from '../triggers'; -import { IAction, ITrigger } from 'src/plugins/ui_actions/public'; +import { IAction, ITrigger, IUiActionsApi } from 'src/plugins/ui_actions/public'; import { Trigger, GetEmbeddableFactory, ViewMode } from '../types'; import { EmbeddableFactory, isErrorEmbeddable } from '../embeddables'; import { EmbeddablePanel } from './embeddable_panel'; @@ -42,6 +42,7 @@ import { } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { EuiBadge } from '@elastic/eui'; const actionRegistry = new Map(); const triggerRegistry = new Map(); @@ -174,6 +175,105 @@ test('HelloWorldContainer in view mode hides edit mode actions', async () => { expect(findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`).length).toBe(0); }); +const renderInEditModeAndOpenContextMenu = async ( + embeddableInputs: any, + getActions: IUiActionsApi['getTriggerCompatibleActions'] = () => Promise.resolve([]) +) => { + const inspector = inspectorPluginMock.createStartContract(); + + const container = new HelloWorldContainer({ id: '123', panels: {}, viewMode: ViewMode.VIEW }, { + getEmbeddableFactory, + } as any); + + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, embeddableInputs); + + const component = mount( + + []) as any} + getEmbeddableFactory={(() => undefined) as any} + notifications={{} as any} + overlays={{} as any} + inspector={inspector} + SavedObjectFinder={() => null} + /> + + ); + + findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); + await nextTick(); + component.update(); + + return { component }; +}; + +test('HelloWorldContainer in edit mode hides disabledActions', async () => { + const action = { + id: 'FOO', + type: 'FOO', + getIconType: () => undefined, + getDisplayName: () => 'foo', + isCompatible: async () => true, + execute: async () => {}, + }; + const getActions = () => Promise.resolve([action]); + + const { component: component1 } = await renderInEditModeAndOpenContextMenu( + { + firstName: 'Bob', + }, + getActions + ); + const { component: component2 } = await renderInEditModeAndOpenContextMenu( + { + firstName: 'Bob', + disabledActions: ['FOO'], + }, + getActions + ); + + const fooContextMenuActionItem1 = findTestSubject(component1, 'embeddablePanelAction-FOO'); + const fooContextMenuActionItem2 = findTestSubject(component2, 'embeddablePanelAction-FOO'); + + expect(fooContextMenuActionItem1.length).toBe(1); + expect(fooContextMenuActionItem2.length).toBe(0); +}); + +test('HelloWorldContainer hides disabled badges', async () => { + const action = { + id: 'BAR', + type: 'BAR', + getIconType: () => undefined, + getDisplayName: () => 'bar', + isCompatible: async () => true, + execute: async () => {}, + }; + const getActions = () => Promise.resolve([action]); + + const { component: component1 } = await renderInEditModeAndOpenContextMenu( + { + firstName: 'Bob', + }, + getActions + ); + const { component: component2 } = await renderInEditModeAndOpenContextMenu( + { + firstName: 'Bob', + disabledActions: ['BAR'], + }, + getActions + ); + + expect(component1.find(EuiBadge).length).toBe(1); + expect(component2.find(EuiBadge).length).toBe(0); +}); + test('HelloWorldContainer in edit mode shows edit mode actions', async () => { const inspector = inspectorPluginMock.createStartContract(); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index cad095a6b0814d..234d8508bb97a4 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -91,18 +91,22 @@ export class EmbeddablePanel extends React.Component { } private async refreshBadges() { - const badges = await this.props.getActions(PANEL_BADGE_TRIGGER, { + let badges: IAction[] = await this.props.getActions(PANEL_BADGE_TRIGGER, { embeddable: this.props.embeddable, }); + if (!this.mounted) return; - if (this.mounted) { - this.setState({ - badges, - }); + const { disabledActions } = this.props.embeddable.getInput(); + if (disabledActions) { + badges = badges.filter(badge => disabledActions.indexOf(badge.id) === -1); } + + this.setState({ + badges, + }); } - public componentWillMount() { + public UNSAFE_componentWillMount() { this.mounted = true; const { embeddable } = this.props; const { parent } = embeddable; @@ -200,10 +204,15 @@ export class EmbeddablePanel extends React.Component { }; private getActionContextMenuPanel = async () => { - const actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { + let actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { embeddable: this.props.embeddable, }); + const { disabledActions } = this.props.embeddable.getInput(); + if (disabledActions) { + actions = actions.filter(action => disabledActions.indexOf(action.id) === -1); + } + const createGetUserData = (overlays: OverlayStart) => async function getUserData(context: { embeddable: IEmbeddable }) { return new Promise<{ title: string | undefined }>(resolve => { diff --git a/src/plugins/embeddable/public/tests/helpers.ts b/src/plugins/embeddable/public/tests/helpers.ts index df3fea0d6b1693..de15ef61c2c608 100644 --- a/src/plugins/embeddable/public/tests/helpers.ts +++ b/src/plugins/embeddable/public/tests/helpers.ts @@ -17,11 +17,27 @@ * under the License. */ -export const expectError = (fn: (...args: any) => any) => { +export const expectErrorAsync = (fn: (...args: unknown[]) => Promise): Promise => { + return fn() + .then(() => { + throw new Error('Expected an error throw.'); + }) + .catch(error => { + if (error.message === 'Expected an error throw.') { + throw error; + } + return error; + }); +}; + +export const expectError = (fn: (...args: any) => any): Error => { try { fn(); throw new Error('Expected an error throw.'); } catch (error) { + if (error.message === 'Expected an error throw.') { + throw error; + } return error; } }; diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts index 48c7904661e515..5a3f28ed76486c 100644 --- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts +++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts @@ -19,11 +19,12 @@ import { useEffect, useState, useRef } from 'react'; -import { HttpServiceBase } from '../../../../../src/core/public'; +import { HttpServiceBase, HttpFetchQuery } from '../../../../../src/core/public'; export interface SendRequestConfig { path: string; method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; + query?: HttpFetchQuery; body?: any; } @@ -48,10 +49,10 @@ export interface UseRequestResponse { export const sendRequest = async ( httpClient: HttpServiceBase, - { path, method, body }: SendRequestConfig + { path, method, body, query }: SendRequestConfig ): Promise => { try { - const response = await httpClient[method](path, { body }); + const response = await httpClient[method](path, { body, query }); return { data: response.data ? response.data : response, @@ -70,6 +71,7 @@ export const useRequest = ( { path, method, + query, body, pollIntervalMs, initialData, @@ -112,6 +114,7 @@ export const useRequest = ( const requestBody = { path, method, + query, body, }; diff --git a/src/plugins/eui_utils/public/eui_utils.test.tsx b/src/plugins/eui_utils/public/eui_utils.test.tsx index 019ca4fcbc18d9..a42eba838fe23f 100644 --- a/src/plugins/eui_utils/public/eui_utils.test.tsx +++ b/src/plugins/eui_utils/public/eui_utils.test.tsx @@ -18,7 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { EuiUtils } from './eui_utils'; diff --git a/src/plugins/expressions/common/expression_types/datatable.ts b/src/plugins/expressions/common/expression_types/datatable.ts index 3c0b5ca9bba583..d58a709349c506 100644 --- a/src/plugins/expressions/common/expression_types/datatable.ts +++ b/src/plugins/expressions/common/expression_types/datatable.ts @@ -59,7 +59,7 @@ export interface Datatable { rows: DatatableRow[]; } -interface SerializedDatatable extends Datatable { +export interface SerializedDatatable extends Datatable { rows: string[][]; } diff --git a/src/plugins/inspector/public/views/data/components/data_table.tsx b/src/plugins/inspector/public/views/data/components/data_table.tsx index d5f2d4645ce0b0..b78a3920804d28 100644 --- a/src/plugins/inspector/public/views/data/components/data_table.tsx +++ b/src/plugins/inspector/public/views/data/components/data_table.tsx @@ -36,7 +36,7 @@ import { i18n } from '@kbn/i18n'; import { DataDownloadOptions } from './download_options'; import { DataViewRow, DataViewColumn } from '../types'; import { TabularData } from '../../../adapters/data/types'; -import { UiSettingsClientContract } from '../../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../../core/public'; interface DataTableFormatState { columns: DataViewColumn[]; @@ -46,7 +46,7 @@ interface DataTableFormatState { interface DataTableFormatProps { data: TabularData; exportTitle: string; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; isFormatted?: boolean; } diff --git a/src/plugins/inspector/public/views/data/components/data_view.test.tsx b/src/plugins/inspector/public/views/data/components/data_view.test.tsx index d067757350b515..55322bf5ec91a2 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.test.tsx +++ b/src/plugins/inspector/public/views/data/components/data_view.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { getDataViewDescription } from '../index'; import { DataAdapter } from '../../../adapters/data'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { UiSettingsClientContract } from '../../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../../core/public'; jest.mock('../lib/export_csv', () => ({ exportAsCsv: jest.fn(), @@ -31,7 +31,7 @@ describe('Inspector Data View', () => { let DataView: any; beforeEach(() => { - const uiSettings = {} as UiSettingsClientContract; + const uiSettings = {} as IUiSettingsClient; DataView = getDataViewDescription(uiSettings); }); diff --git a/src/plugins/inspector/public/views/data/components/data_view.tsx b/src/plugins/inspector/public/views/data/components/data_view.tsx index 34e0bfaa526933..91f42a54f64d0c 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.tsx +++ b/src/plugins/inspector/public/views/data/components/data_view.tsx @@ -32,7 +32,7 @@ import { import { DataTableFormat } from './data_table'; import { InspectorViewProps, Adapters } from '../../../types'; import { TabularLoaderOptions, TabularData, TabularCallback } from '../../../adapters/data/types'; -import { UiSettingsClientContract } from '../../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../../core/public'; interface DataViewComponentState { tabularData: TabularData | null; @@ -42,7 +42,7 @@ interface DataViewComponentState { } interface DataViewComponentProps extends InspectorViewProps { - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } export class DataViewComponent extends Component { diff --git a/src/plugins/inspector/public/views/data/index.tsx b/src/plugins/inspector/public/views/data/index.tsx index a33bf5ebf1f529..0cd88442bf8f87 100644 --- a/src/plugins/inspector/public/views/data/index.tsx +++ b/src/plugins/inspector/public/views/data/index.tsx @@ -21,10 +21,10 @@ import { i18n } from '@kbn/i18n'; import { DataViewComponent } from './components/data_view'; import { Adapters, InspectorViewDescription, InspectorViewProps } from '../../types'; -import { UiSettingsClientContract } from '../../../../../core/public'; +import { IUiSettingsClient } from '../../../../../core/public'; export const getDataViewDescription = ( - uiSettings: UiSettingsClientContract + uiSettings: IUiSettingsClient ): InspectorViewDescription => ({ title: i18n.translate('inspector.data.dataTitle', { defaultMessage: 'Data', diff --git a/src/plugins/inspector/public/views/requests/_requests.scss b/src/plugins/inspector/public/views/requests/_requests.scss index feaef21fc4810d..273c9d0ccba2b1 100644 --- a/src/plugins/inspector/public/views/requests/_requests.scss +++ b/src/plugins/inspector/public/views/requests/_requests.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/button/variables'; - .insRequestDetailsStats__icon { margin-right: $euiSizeS; } diff --git a/src/plugins/kibana_react/public/code_editor/README.md b/src/plugins/kibana_react/public/code_editor/README.md new file mode 100644 index 00000000000000..887a9c99909155 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/README.md @@ -0,0 +1,13 @@ +# Code Editor Component + +This re-usable code editor component was built as a layer of abstraction on top of the [Monaco Code Editor](https://microsoft.github.io/monaco-editor/) (and the [React Monaco Editor component](https://github.com/react-monaco-editor/react-monaco-editor)). The goal of this component is to expose a set of the most-used, most-helpful features from Monaco in a way that's easy to use out of the box. If a use case requires additional features, this component still allows access to all other Monaco features. + +This editor component allows easy access to: +* [Syntax highlighting (including custom language highlighting)](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages) +* [Suggestion/autocompletion widget](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example) +* Function signature widget +* [Hover widget](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-hover-provider-example) + +[_TODO: Examples of each_](https://github.com/elastic/kibana/issues/43812) + +The Monaco editor doesn't automatically resize the editor area on window or container resize so this component includes a [resize detector](https://github.com/maslianok/react-resize-detector) to cause the Monaco editor to re-layout and adjust its size when the window or container size changes \ No newline at end of file diff --git a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap new file mode 100644 index 00000000000000..2800c6cd7c198a --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`is rendered 1`] = ` + + + + +`; diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx new file mode 100644 index 00000000000000..2c305c4ea97da3 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx @@ -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 React from 'react'; +import { CodeEditor } from './code_editor'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { shallow } from 'enzyme'; + +import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js'; + +// A sample language definition with a few example tokens +const simpleLogLang: monacoEditor.languages.IMonarchLanguage = { + tokenizer: { + root: [ + [/\[error.*/, 'constant'], + [/\[notice.*/, 'variable'], + [/\[info.*/, 'string'], + [/\[[a-zA-Z 0-9:]+\]/, 'tag'], + ], + }, +}; + +monacoEditor.languages.register({ id: 'loglang' }); +monacoEditor.languages.setMonarchTokensProvider('loglang', simpleLogLang); + +const logs = ` +[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice! +[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed +[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome +`; + +test('is rendered', () => { + const component = shallow( + {}} /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('editor mount setup', () => { + const suggestionProvider = { + provideCompletionItems: ( + model: monacoEditor.editor.ITextModel, + position: monacoEditor.Position + ) => ({ suggestions: [] }), + }; + const signatureProvider = { + provideSignatureHelp: () => ({ signatures: [], activeParameter: 0, activeSignature: 0 }), + }; + const hoverProvider = { + provideHover: (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => ({ + contents: [], + }), + }; + + const editorWillMount = jest.fn(); + + monacoEditor.languages.onLanguage = jest.fn((languageId, func) => { + expect(languageId).toBe('loglang'); + + // Call the function immediately so we can see our providers + // get setup without a monaco editor setting up completely + func(); + }) as any; + + monacoEditor.languages.registerCompletionItemProvider = jest.fn(); + monacoEditor.languages.registerSignatureHelpProvider = jest.fn(); + monacoEditor.languages.registerHoverProvider = jest.fn(); + + monacoEditor.editor.defineTheme = jest.fn(); + + const wrapper = shallow( + {}} + editorWillMount={editorWillMount} + suggestionProvider={suggestionProvider} + signatureProvider={signatureProvider} + hoverProvider={hoverProvider} + /> + ); + + const instance = wrapper.instance() as CodeEditor; + instance._editorWillMount(monacoEditor); + + // Verify our mount callback will be called + expect(editorWillMount.mock.calls.length).toBe(1); + + // Verify our theme will be setup + expect((monacoEditor.editor.defineTheme as jest.Mock).mock.calls.length).toBe(1); + + // Verify our language features have been registered + expect((monacoEditor.languages.onLanguage as jest.Mock).mock.calls.length).toBe(1); + expect( + (monacoEditor.languages.registerCompletionItemProvider as jest.Mock).mock.calls.length + ).toBe(1); + expect( + (monacoEditor.languages.registerSignatureHelpProvider as jest.Mock).mock.calls.length + ).toBe(1); + expect((monacoEditor.languages.registerHoverProvider as jest.Mock).mock.calls.length).toBe(1); +}); diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx new file mode 100644 index 00000000000000..0ae77995c0502f --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -0,0 +1,176 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 ReactResizeDetector from 'react-resize-detector'; +import MonacoEditor, { EditorDidMount, EditorWillMount } from 'react-monaco-editor'; + +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import 'monaco-editor/esm/vs/base/common/worker/simpleWorker'; +import 'monaco-editor/esm/vs/base/worker/defaultWorkerFactory'; + +import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js'; +import 'monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js'; + +import 'monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js'; // Needed for suggestions +import 'monaco-editor/esm/vs/editor/contrib/hover/hover.js'; // Needed for hover +import 'monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js'; // Needed for signature + +import { LIGHT_THEME, DARK_THEME } from './editor_theme'; + +export interface Props { + /** Width of editor. Defaults to 100%. */ + width?: string | number; + + /** Height of editor. Defaults to 100%. */ + height?: string | number; + + /** ID of the editor language */ + languageId: string; + + /** Value of the editor */ + value: string; + + /** Function invoked when text in editor is changed */ + onChange: (value: string) => void; + + /** + * Options for the Monaco Code Editor + * Documentation of options can be found here: + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.ieditorconstructionoptions.html + */ + options?: monacoEditor.editor.IEditorConstructionOptions; + + /** + * Suggestion provider for autocompletion + * Documentation for the provider can be found here: + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.completionitemprovider.html + */ + suggestionProvider?: monacoEditor.languages.CompletionItemProvider; + + /** + * Signature provider for function parameter info + * Documentation for the provider can be found here: + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.signaturehelpprovider.html + */ + signatureProvider?: monacoEditor.languages.SignatureHelpProvider; + + /** + * Hover provider for hover documentation + * Documentation for the provider can be found here: + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.hoverprovider.html + */ + hoverProvider?: monacoEditor.languages.HoverProvider; + + /** + * Function called before the editor is mounted in the view + */ + editorWillMount?: EditorWillMount; + /** + * Function called before the editor is mounted in the view + * and completely replaces the setup behavior called by the component + */ + overrideEditorWillMount?: EditorWillMount; + + /** + * Function called after the editor is mounted in the view + */ + editorDidMount?: EditorDidMount; + + /** + * Should the editor use the dark theme + */ + useDarkTheme?: boolean; +} + +export class CodeEditor extends React.Component { + _editor: monacoEditor.editor.IStandaloneCodeEditor | null = null; + + _editorWillMount = (monaco: typeof monacoEditor) => { + if (this.props.overrideEditorWillMount) { + this.props.overrideEditorWillMount(monaco); + return; + } + + if (this.props.editorWillMount) { + this.props.editorWillMount(monaco); + } + + monaco.languages.onLanguage(this.props.languageId, () => { + if (this.props.suggestionProvider) { + monaco.languages.registerCompletionItemProvider( + this.props.languageId, + this.props.suggestionProvider + ); + } + + if (this.props.signatureProvider) { + monaco.languages.registerSignatureHelpProvider( + this.props.languageId, + this.props.signatureProvider + ); + } + + if (this.props.hoverProvider) { + monaco.languages.registerHoverProvider(this.props.languageId, this.props.hoverProvider); + } + }); + + // Register the theme + monaco.editor.defineTheme('euiColors', this.props.useDarkTheme ? DARK_THEME : LIGHT_THEME); + }; + + _editorDidMount = ( + editor: monacoEditor.editor.IStandaloneCodeEditor, + monaco: typeof monacoEditor + ) => { + this._editor = editor; + + if (this.props.editorDidMount) { + this.props.editorDidMount(editor, monaco); + } + }; + + render() { + const { languageId, value, onChange, width, height, options } = this.props; + + return ( + + + + + ); + } + + _updateDimensions = () => { + if (this._editor) { + this._editor.layout(); + } + }; +} diff --git a/x-pack/legacy/plugins/canvas/public/components/editor/editor.scss b/src/plugins/kibana_react/public/code_editor/editor.scss similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/editor/editor.scss rename to src/plugins/kibana_react/public/code_editor/editor.scss diff --git a/src/plugins/kibana_react/public/code_editor/editor_theme.ts b/src/plugins/kibana_react/public/code_editor/editor_theme.ts new file mode 100644 index 00000000000000..41702f1b3fc35c --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/editor_theme.ts @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; + +import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; +import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; + +// NOTE: For talk around where this theme information will ultimately live, +// please see this discuss issue: https://github.com/elastic/kibana/issues/43814 + +export function createTheme( + euiTheme: typeof darkTheme | typeof lightTheme, + selectionBackgroundColor: string +): monacoEditor.editor.IStandaloneThemeData { + return { + base: 'vs', + inherit: true, + rules: [ + { + token: '', + foreground: euiTheme.euiColorDarkestShade, + background: euiTheme.euiColorEmptyShade, + }, + { token: 'invalid', foreground: euiTheme.euiColorAccent }, + { token: 'emphasis', fontStyle: 'italic' }, + { token: 'strong', fontStyle: 'bold' }, + + { token: 'variable', foreground: euiTheme.euiColorPrimary }, + { token: 'variable.predefined', foreground: euiTheme.euiColorSecondary }, + { token: 'constant', foreground: euiTheme.euiColorAccent }, + { token: 'comment', foreground: euiTheme.euiColorMediumShade }, + { token: 'number', foreground: euiTheme.euiColorAccent }, + { token: 'number.hex', foreground: euiTheme.euiColorAccent }, + { token: 'regexp', foreground: euiTheme.euiColorDanger }, + { token: 'annotation', foreground: euiTheme.euiColorMediumShade }, + { token: 'type', foreground: euiTheme.euiColorVis0 }, + + { token: 'delimiter', foreground: euiTheme.euiColorDarkestShade }, + { token: 'delimiter.html', foreground: euiTheme.euiColorDarkShade }, + { token: 'delimiter.xml', foreground: euiTheme.euiColorPrimary }, + + { token: 'tag', foreground: euiTheme.euiColorDanger }, + { token: 'tag.id.jade', foreground: euiTheme.euiColorPrimary }, + { token: 'tag.class.jade', foreground: euiTheme.euiColorPrimary }, + { token: 'meta.scss', foreground: euiTheme.euiColorAccent }, + { token: 'metatag', foreground: euiTheme.euiColorSecondary }, + { token: 'metatag.content.html', foreground: euiTheme.euiColorDanger }, + { token: 'metatag.html', foreground: euiTheme.euiColorMediumShade }, + { token: 'metatag.xml', foreground: euiTheme.euiColorMediumShade }, + { token: 'metatag.php', fontStyle: 'bold' }, + + { token: 'key', foreground: euiTheme.euiColorWarning }, + { token: 'string.key.json', foreground: euiTheme.euiColorDanger }, + { token: 'string.value.json', foreground: euiTheme.euiColorPrimary }, + + { token: 'attribute.name', foreground: euiTheme.euiColorDanger }, + { token: 'attribute.name.css', foreground: euiTheme.euiColorSecondary }, + { token: 'attribute.value', foreground: euiTheme.euiColorPrimary }, + { token: 'attribute.value.number', foreground: euiTheme.euiColorWarning }, + { token: 'attribute.value.unit', foreground: euiTheme.euiColorWarning }, + { token: 'attribute.value.html', foreground: euiTheme.euiColorPrimary }, + { token: 'attribute.value.xml', foreground: euiTheme.euiColorPrimary }, + + { token: 'string', foreground: euiTheme.euiColorDanger }, + { token: 'string.html', foreground: euiTheme.euiColorPrimary }, + { token: 'string.sql', foreground: euiTheme.euiColorDanger }, + { token: 'string.yaml', foreground: euiTheme.euiColorPrimary }, + + { token: 'keyword', foreground: euiTheme.euiColorPrimary }, + { token: 'keyword.json', foreground: euiTheme.euiColorPrimary }, + { token: 'keyword.flow', foreground: euiTheme.euiColorWarning }, + { token: 'keyword.flow.scss', foreground: euiTheme.euiColorPrimary }, + + { token: 'operator.scss', foreground: euiTheme.euiColorDarkShade }, + { token: 'operator.sql', foreground: euiTheme.euiColorMediumShade }, + { token: 'operator.swift', foreground: euiTheme.euiColorMediumShade }, + { token: 'predefined.sql', foreground: euiTheme.euiColorMediumShade }, + ], + colors: { + 'editor.foreground': euiTheme.euiColorDarkestShade, + 'editor.background': euiTheme.euiColorEmptyShade, + 'editorLineNumber.foreground': euiTheme.euiColorDarkShade, + 'editorLineNumber.activeForeground': euiTheme.euiColorDarkShade, + 'editorIndentGuide.background': euiTheme.euiColorLightShade, + 'editor.selectionBackground': selectionBackgroundColor, + 'editorWidget.border': euiTheme.euiColorLightShade, + 'editorWidget.background': euiTheme.euiColorLightestShade, + }, + }; +} + +const DARK_THEME = createTheme(darkTheme, '#343551'); +const LIGHT_THEME = createTheme(lightTheme, '#E3E4ED'); + +export { DARK_THEME, LIGHT_THEME }; diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx new file mode 100644 index 00000000000000..3c15feff3f2abc --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { useUiSetting } from '../ui_settings'; +import { CodeEditor as BaseEditor, Props } from './code_editor'; + +export const CodeEditor: React.FunctionComponent = props => { + const darkMode = useUiSetting('theme:darkMode'); + + return ; +}; diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap b/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap index 979b12847097aa..07e2dc7f35f808 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap +++ b/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap @@ -13,46 +13,43 @@ exports[`is rendered 1`] = ` In full screen mode, press ESC to exit.

    -
    - + - + + + + +
    diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss b/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss index c42d8b7c4a66d4..e810fe0ccdba6f 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss +++ b/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss @@ -1,17 +1,9 @@ -.dshExitFullScreenButton { - text-align: center; - width: 100%; - height: 0; - bottom: 0; - position: absolute; -} - /** * 1. override the z-index: 1 applied to all non-eui elements that are in :focus via kui * - see packages/kbn-ui-framework/src/global_styling/reset/_reset.scss */ -.dshExitFullScreenButton__mode { +.dshExitFullScreenButton { height: $euiSizeXXL; left: 0; bottom: 0; @@ -59,18 +51,19 @@ color: $euiColorEmptyShade; line-height: $euiSizeXXL; display: inline-block; + font-size: $euiFontSizeS; height: $euiSizeXXL; position: absolute; left: calc(100% + #{$euiSize}); /* 1 */ top: 0px; bottom: 0px; white-space: nowrap; - padding: 0px $euiSizeS 0px $euiSizeM; + padding: 0px $euiSizeXS 0px $euiSizeM; transition: all .2s ease; transform: translateX(-100%); z-index: -1; - - .kuiIcon { - margin-left: $euiSizeS; + + .euiIcon { + margin-left: $euiSizeXS; } } 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 a880d3c6cf87c3..5ce508ec1ed5b2 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 @@ -20,9 +20,7 @@ import { i18n } from '@kbn/i18n'; import React, { PureComponent } from 'react'; import { EuiScreenReaderOnly, keyCodes } from '@elastic/eui'; - -// @ts-ignore -import { KuiButton } from '@kbn/ui-framework/components'; +import { EuiIcon } from '@elastic/eui'; export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; @@ -35,7 +33,7 @@ class ExitFullScreenButtonUi extends PureComponent { } }; - public componentWillMount() { + public UNSAFE_componentWillMount() { document.addEventListener('keydown', this.onKeyDown, false); } @@ -53,16 +51,15 @@ class ExitFullScreenButtonUi extends PureComponent { })}

    -
    - +
    ); diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 46f330ea0a2c5e..81ef707ed0eba3 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -17,6 +17,7 @@ * under the License. */ +export * from './code_editor'; export * from './saved_objects'; export * from './exit_full_screen_button'; export * from './context'; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx index 5b3d638f9e9351..58b396d57639b5 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx @@ -35,7 +35,7 @@ import { IconType } from '@elastic/eui'; import { shallow } from 'enzyme'; import React from 'react'; import * as sinon from 'sinon'; -import { SavedObjectFinder } from './saved_object_finder'; +import { SavedObjectFinderUi as SavedObjectFinder } from './saved_object_finder'; // eslint-disable-next-line import { coreMock } from '../../../../core/public/mocks'; @@ -251,6 +251,7 @@ describe('SavedObjectsFinder', () => { it('should include additional fields in search if listed in meta data', async () => { const core = coreMock.createStart(); core.uiSettings.get.mockImplementation(() => 10); + (core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] }); const wrapper = shallow( { describe('loading state', () => { it('should display a spinner during initial loading', () => { const core = coreMock.createStart(); + (core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] }); const wrapper = shallow( { +class SavedObjectFinderUi extends React.Component< + SavedObjectFinderUiProps, + SavedObjectFinderState +> { public static propTypes = { onChoose: PropTypes.func, noItemsMessage: PropTypes.node, @@ -174,7 +181,7 @@ class SavedObjectFinder extends React.Component { + const { services } = useKibana(); + return ( + + ); +}; + +export { SavedObjectFinder, SavedObjectFinderUi }; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index dde8efa7e11066..4bb7ce75073aee 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -37,7 +37,7 @@ import { EuiConfirmModal, EuiCallOut, } from '@elastic/eui'; -import { ToastsStart, UiSettingsClientContract } from 'kibana/public'; +import { ToastsStart, IUiSettingsClient } from 'kibana/public'; import { toMountPoint } from '../util'; export const EMPTY_FILTER = ''; @@ -66,7 +66,7 @@ export interface TableListViewProps { tableColumns: Column[]; tableListTitle: string; toastNotifications: ToastsStart; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } export interface TableListViewState { @@ -112,7 +112,7 @@ class TableListView extends React.Component { diff --git a/src/plugins/kibana_utils/README.md b/src/plugins/kibana_utils/README.md index 61ceea2b183856..5501505dbb7e27 100644 --- a/src/plugins/kibana_utils/README.md +++ b/src/plugins/kibana_utils/README.md @@ -2,4 +2,4 @@ Utilities for building Kibana plugins. -- [Store reactive serializable app state in state containers, `createStore`](./docs/store/README.md). +- [State containers](./docs/state_containers/README.md). diff --git a/src/plugins/kibana_utils/demos/demos.test.ts b/src/plugins/kibana_utils/demos/demos.test.ts new file mode 100644 index 00000000000000..4e792ceef117a5 --- /dev/null +++ b/src/plugins/kibana_utils/demos/demos.test.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. + */ + +import { result as counterResult } from './state_containers/counter'; +import { result as todomvcResult } from './state_containers/todomvc'; + +describe('demos', () => { + describe('state containers', () => { + test('counter demo works', () => { + expect(counterResult).toBe(10); + }); + + test('TodoMVC demo works', () => { + expect(todomvcResult).toEqual([ + { id: 0, text: 'Learning state containers', completed: true }, + { id: 1, text: 'Learning transitions...', completed: true }, + ]); + }); + }); +}); diff --git a/src/plugins/kibana_utils/demos/state_containers/counter.ts b/src/plugins/kibana_utils/demos/state_containers/counter.ts new file mode 100644 index 00000000000000..643763cc4cee93 --- /dev/null +++ b/src/plugins/kibana_utils/demos/state_containers/counter.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 { createStateContainer } from '../../public/state_containers'; + +const container = createStateContainer(0, { + increment: (cnt: number) => (by: number) => cnt + by, + double: (cnt: number) => () => cnt * 2, +}); + +container.transitions.increment(5); +container.transitions.double(); + +console.log(container.get()); // eslint-disable-line + +export const result = container.get(); diff --git a/src/plugins/kibana_utils/demos/state_containers/todomvc.ts b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts new file mode 100644 index 00000000000000..6d0c960e2a5b26 --- /dev/null +++ b/src/plugins/kibana_utils/demos/state_containers/todomvc.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 { createStateContainer, PureTransition } from '../../public/state_containers'; + +export interface TodoItem { + text: string; + completed: boolean; + id: number; +} + +export type TodoState = TodoItem[]; + +export const defaultState: TodoState = [ + { + id: 0, + text: 'Learning state containers', + completed: false, + }, +]; + +export interface TodoActions { + add: PureTransition; + edit: PureTransition; + delete: PureTransition; + complete: PureTransition; + completeAll: PureTransition; + clearCompleted: PureTransition; +} + +export const pureTransitions: TodoActions = { + add: state => todo => [...state, todo], + edit: state => todo => state.map(item => (item.id === todo.id ? { ...item, ...todo } : item)), + delete: state => id => state.filter(item => item.id !== id), + complete: state => id => + state.map(item => (item.id === id ? { ...item, completed: true } : item)), + completeAll: state => () => state.map(item => ({ ...item, completed: true })), + clearCompleted: state => () => state.filter(({ completed }) => !completed), +}; + +const container = createStateContainer(defaultState, pureTransitions); + +container.transitions.add({ + id: 1, + text: 'Learning transitions...', + completed: false, +}); +container.transitions.complete(0); +container.transitions.complete(1); + +console.log(container.get()); // eslint-disable-line + +export const result = container.get(); diff --git a/src/plugins/kibana_utils/docs/state_containers/README.md b/src/plugins/kibana_utils/docs/state_containers/README.md new file mode 100644 index 00000000000000..3b7a8b8bd4621d --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/README.md @@ -0,0 +1,50 @@ +# State containers + +State containers are Redux-store-like objects meant to help you manage state in +your services or apps. + +- State containers are strongly typed, you will get TypeScript autocompletion suggestions from + your editor when accessing state, executing transitions and using React helpers. +- State containers can be easily hooked up with your React components. +- State containers can be used without React, too. +- State containers provide you central place where to store state, instead of spreading + state around multiple RxJs observables, which you need to coordinate. With state + container you can always access the latest state snapshot synchronously. +- Unlike Redux, state containers are less verbose, see example below. + + +## Example + +```ts +import { createStateContainer } from 'src/plugins/kibana_utils'; + +const container = createStateContainer(0, { + increment: (cnt: number) => (by: number) => cnt + by, + double: (cnt: number) => () => cnt * 2, +}); + +container.transitions.increment(5); +container.transitions.double(); +console.log(container.get()); // 10 +``` + + +## Demos + +See demos [here](../../demos/state_containers/). + +You can run them with + +``` +npx -q ts-node src/plugins/kibana_utils/demos/state_containers/counter.ts +npx -q ts-node src/plugins/kibana_utils/demos/state_containers/todomvc.ts +``` + + +## Reference + +- [Creating a state container](./creation.md). +- [State transitions](./transitions.md). +- [Using with React](./react.md). +- [Using without React`](./no_react.md). +- [Parallels with Redux](./redux.md). diff --git a/src/plugins/kibana_utils/docs/state_containers/creation.md b/src/plugins/kibana_utils/docs/state_containers/creation.md new file mode 100644 index 00000000000000..66d28bbd8603f0 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/creation.md @@ -0,0 +1,38 @@ +# Creating a *state container* + +Create a TypeScript annotation of your state shape. + +```ts +interface MySavedObject { + id: string; + name: string; +} + +interface MyState { + uiMode: 'dark' | 'light'; + isPanelOpen: boolean; + users: { + [id: string]: MySavedObject; + }; +} +``` + +Create default state of your container. + +```ts +const defaultState: MyState = { + uiMode: 'dark', + isPanelOpen: false, + users: {}, +}; +``` + +Create your a state container. + +```ts +import { createStateContainer } from 'src/plugins/kibana_utils'; + +const container = createStateContainer(defaultState, {}); + +console.log(container.get()); +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/no_react.md b/src/plugins/kibana_utils/docs/state_containers/no_react.md new file mode 100644 index 00000000000000..7a15483d83b44f --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/no_react.md @@ -0,0 +1,13 @@ +# Consuming state in non-React setting + +To read the current `state` of the store use `.get()` method. + +```ts +store.get(); +``` + +To listen for latest state changes use `.state$` observable. + +```ts +store.state$.subscribe(state => { ... }); +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react.md b/src/plugins/kibana_utils/docs/state_containers/react.md new file mode 100644 index 00000000000000..363fd9253d44fd --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react.md @@ -0,0 +1,41 @@ +# React + +`createStateContainerReactHelpers` factory allows you to easily use state containers with React. + + +## Example + + +```ts +import { createStateContainer, createStateContainerReactHelpers } from 'src/plugins/kibana_utils'; + +const container = createStateContainer({}, {}); +export const { + Provider, + Consumer, + context, + useContainer, + useState, + useTransitions, + useSelector, + connect, +} = createStateContainerReactHelpers(); +``` + +Wrap your app with ``. + +```tsx + + + +``` + + +## Reference + +- [`useContainer()`](./react/use_container.md) +- [`useState()`](./react/use_state.md) +- [`useSelector()`](./react/use_selector.md) +- [`useTransitions()`](./react/use_transitions.md) +- [`connect()()`](./react/connect.md) +- [Context](./react/context.md) diff --git a/src/plugins/kibana_utils/docs/state_containers/react/connect.md b/src/plugins/kibana_utils/docs/state_containers/react/connect.md new file mode 100644 index 00000000000000..56b7e0fbc56736 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/connect.md @@ -0,0 +1,22 @@ +# `connect()()` higher order component + +Use `connect()()` higher-order-component to inject props from state into your component. + +```tsx +interface Props { + name: string; + punctuation: '.' | ',' | '!', +} +const Demo: React.FC = ({ name, punctuation }) => +
    Hello, {name}{punctuation}
    ; + +const store = createStateContainer({ userName: 'John' }); +const { Provider, connect } = createStateContainerReactHelpers(store); + +const mapStateToProps = ({ userName }) => ({ name: userName }); +const DemoConnected = connect(mapStateToProps)(Demo); + + + + +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/context.md b/src/plugins/kibana_utils/docs/state_containers/react/context.md new file mode 100644 index 00000000000000..33f084fdfe9d7b --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/context.md @@ -0,0 +1,24 @@ +# React context + +`createStateContainerReactHelpers` returns `` and `` components +as well as `context` React context object. + +```ts +export const { + Provider, + Consumer, + context, +} = createStateContainerReactHelpers(); +``` + +`` and `` are just regular React context components. + +```tsx + +
    + {container => +
    {JSON.stringify(container.get())}
    + }
    +
    +
    +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_container.md b/src/plugins/kibana_utils/docs/state_containers/react/use_container.md new file mode 100644 index 00000000000000..5e698edb8529cc --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_container.md @@ -0,0 +1,10 @@ +# `useContainer` hook + +`useContainer` React hook will simply return you `container` object from React context. + +```tsx +const Demo = () => { + const store = useContainer(); + return
    {store.get().isDarkMode ? '🌑' : '☀️'}
    ; +}; +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_selector.md b/src/plugins/kibana_utils/docs/state_containers/react/use_selector.md new file mode 100644 index 00000000000000..2ecf772fba367a --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_selector.md @@ -0,0 +1,20 @@ +# `useSelector()` hook + +With `useSelector` React hook you specify a selector function, which will pick specific +data from the state. *Your component will update only when that specific part of the state changes.* + +```tsx +const selector = state => state.isDarkMode; +const Demo = () => { + const isDarkMode = useSelector(selector); + return
    {isDarkMode ? '🌑' : '☀️'}
    ; +}; +``` + +As an optional second argument for `useSelector` you can provide a `comparator` function, which +compares currently selected value with the previous and your component will re-render only if +`comparator` returns `true`. By default it uses [`fast-deep-equal`](https://github.com/epoberezkin/fast-deep-equal). + +``` +useSelector(selector, comparator?) +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_state.md b/src/plugins/kibana_utils/docs/state_containers/react/use_state.md new file mode 100644 index 00000000000000..5db1d46897aad2 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_state.md @@ -0,0 +1,11 @@ +# `useState()` hook + +- `useState` hook returns you directly the state of the container. +- It also forces component to re-render every time state changes. + +```tsx +const Demo = () => { + const { isDarkMode } = useState(); + return
    {isDarkMode ? '🌑' : '☀️'}
    ; +}; +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_transitions.md b/src/plugins/kibana_utils/docs/state_containers/react/use_transitions.md new file mode 100644 index 00000000000000..c6783bf0e0f0a6 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_transitions.md @@ -0,0 +1,17 @@ +# `useTransitions` hook + +Access [state transitions](../transitions.md) by `useTransitions` React hook. + +```tsx +const Demo = () => { + const { isDarkMode } = useState(); + const { setDarkMode } = useTransitions(); + return ( + <> +
    {isDarkMode ? '🌑' : '☀️'}
    + + + + ); +}; +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/redux.md b/src/plugins/kibana_utils/docs/state_containers/redux.md new file mode 100644 index 00000000000000..1a60d841a8b75d --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/redux.md @@ -0,0 +1,40 @@ +# Redux + +State containers similar to Redux stores but without the boilerplate. + +State containers expose Redux-like API: + +```js +container.getState() +container.dispatch() +container.replaceReducer() +container.subscribe() +container.addMiddleware() +``` + +State containers have a reducer and every time you execute a state transition it +actually dispatches an "action". For example, this + +```js +container.transitions.increment(25); +``` + +is equivalent to + +```js +container.dispatch({ + type: 'increment', + args: [25], +}); +``` + +Because all transitions happen through `.dispatch()` interface, you can add middleware—similar how you +would do with Redux—to monitor or intercept transitions. + +For example, you can add `redux-logger` middleware to log in console all transitions happening with your store. + +```js +import logger from 'redux-logger'; + +container.addMiddleware(logger); +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/transitions.md b/src/plugins/kibana_utils/docs/state_containers/transitions.md new file mode 100644 index 00000000000000..51d52cdf3daaf9 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/transitions.md @@ -0,0 +1,61 @@ +# State transitions + +*State transitions* describe possible state changes over time. Transitions are pure functions which +receive `state` object and other—optional—arguments and must return a new `state` object back. + +```ts +type Transition = (state: State) => (...args) => State; +``` + +Transitions must not mutate `state` object in-place, instead they must return a +shallow copy of it, e.g. `{ ...state }`. Example: + +```ts +const setUiMode: PureTransition = state => uiMode => ({ ...state, uiMode }); +``` + +You provide transitions as a second argument when you create your state container. + +```ts +import { createStateContainer } from 'src/plugins/kibana_utils'; + +const container = createStateContainer(0, { + increment: (cnt: number) => (by: number) => cnt + by, + double: (cnt: number) => () => cnt * 2, +}); +``` + +Now you can execute the transitions by calling them with only optional parameters (`state` is +provided to your transitions automatically). + +```ts +container.transitions.increment(25); +container.transitions.increment(5); +container.state; // 30 +``` + +Your transitions are bound to the container so you can treat each of them as a +standalone function for export. + +```ts +const defaultState = { + uiMode: 'light', +}; + +const container = createStateContainer(defaultState, { + setUiMode: state => uiMode => ({ ...state, uiMode }), + resetUiMode: state => () => ({ ...state, uiMode: defaultState.uiMode }), +}); + +export const { + setUiMode, + resetUiMode +} = container.transitions; +``` + +You can add TypeScript annotations for your transitions as the second generic argument +to `createStateContainer()` function. + +```ts +const container = createStateContainer(defaultState, pureTransitions); +``` diff --git a/src/plugins/kibana_utils/docs/store/README.md b/src/plugins/kibana_utils/docs/store/README.md deleted file mode 100644 index e1cb098fe04ce6..00000000000000 --- a/src/plugins/kibana_utils/docs/store/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# State containers - -- State containers for holding serializable state. -- [Each plugin/app that needs runtime state will create a *store* using `store = createStore()`](./creation.md). -- [*Store* can be updated using mutators `mutators = store.createMutators({ ... })`](./mutators.md). -- [*Store* can be connected to React `{Provider, connect} = createContext(store)`](./react.md). -- [In no-React setting *store* is consumed using `store.get()` and `store.state$`](./getters.md). -- [Under-the-hood uses Redux `store.redux`](./redux.md) (but you should never need it explicitly). -- [See idea doc with samples and rationale](https://docs.google.com/document/d/18eitHkcyKSsEHUfUIqFKChc8Pp62Z4gcRxdu903hbA0/edit#heading=h.iaxc9whxifl5). diff --git a/src/plugins/kibana_utils/docs/store/creation.md b/src/plugins/kibana_utils/docs/store/creation.md deleted file mode 100644 index b0184ad45eb847..00000000000000 --- a/src/plugins/kibana_utils/docs/store/creation.md +++ /dev/null @@ -1,43 +0,0 @@ -# Creating a *state container* - -Create a TypeScript annotation of your state shape. - -```ts -interface MySavedObject { - id: string; - name: string; -} - -interface MyState { - uiMode: 'dark' | 'light'; - isPanelOpen: boolean; - users: { - [id: string]: MySavedObject; - }; -} -``` - -Create default state of your *store*. - -```ts -const defaultState: MyState = { - uiMode: 'dark', - isPanelOpen: false, - users: {}, -}; -``` - -Create your state container, i.e *store*. - -```ts -import { createStore } from 'kibana-utils'; - -const store = createStore(defaultState); -console.log(store.get()); -``` - -> ##### N.B. -> -> State must always be an object `{}`. -> -> You cannot create a store out of an array, e.g ~~`createStore([])`~~. diff --git a/src/plugins/kibana_utils/docs/store/getters.md b/src/plugins/kibana_utils/docs/store/getters.md deleted file mode 100644 index 508d0c6ebc18d8..00000000000000 --- a/src/plugins/kibana_utils/docs/store/getters.md +++ /dev/null @@ -1,13 +0,0 @@ -# Reading state - -To read the current `state` of the store use `.get()` method. - -```ts -store.get(); -``` - -To listen for latest state changes use `.state$` observable. - -```ts -store.state$.subscribe(state => { ... }); -``` diff --git a/src/plugins/kibana_utils/docs/store/mutators.md b/src/plugins/kibana_utils/docs/store/mutators.md deleted file mode 100644 index 9db1b1bb60b3cc..00000000000000 --- a/src/plugins/kibana_utils/docs/store/mutators.md +++ /dev/null @@ -1,70 +0,0 @@ -# Mutators - -State *mutators* are pure functions which receive `state` object and other—optional—arguments -and must return a new `state` object back. - -```ts -type Mutator = (state: State) => (...args) => State; -``` - -Mutator must not mutate `state` object in-place, instead it should return a -shallow copy of it, e.g. `{ ...state }`. - -```ts -const setUiMode: Mutator = state => uiMode => ({ ...state, uiMode }); -``` - -You create mutators using `.createMutator(...)` method. - -```ts -const store = createStore({uiMode: 'light'}); -const mutators = store.createMutators({ - setUiMode: state => uiMode => ({ ...state, uiMode }), -}); -``` - -Now you can use your mutators by calling them with only optional parameters (`state` is -provided to your mutator automatically). - -```ts -mutators.setUiMode('dark'); -``` - -Your mutators are bound to the `store` so you can treat each of them as a -standalone function for export. - -```ts -const { setUiMode, resetUiMode } = store.createMutators({ - setUiMode: state => uiMode => ({ ...state, uiMode }), - resetUiMode: state => () => ({ ...state, uiMode: 'light' }), -}); - -export { - setUiMode, - resetUiMode, -}; -``` - -The mutators you create are also available on the `store` object. - -```ts -const store = createStore({ cnt: 0 }); -store.createMutators({ - add: state => value => ({ ...state, cnt: state.cnt + value }), -}); - -store.mutators.add(5); -store.get(); // { cnt: 5 } -``` - -You can add TypeScript annotations to your `.mutators` property of `store` object. - -```ts -const store = createStore<{ - cnt: number; -}, { - add: (value: number) => void; -}>({ - cnt: 0 -}); -``` diff --git a/src/plugins/kibana_utils/docs/store/react.md b/src/plugins/kibana_utils/docs/store/react.md deleted file mode 100644 index 68a016ed6d3cac..00000000000000 --- a/src/plugins/kibana_utils/docs/store/react.md +++ /dev/null @@ -1,101 +0,0 @@ -# React - -`createContext` factory allows you to easily use state containers with React. - -```ts -import { createStore, createContext } from 'kibana-utils'; - -const store = createStore({}); -const { - Provider, - Consumer, - connect, - context, - useStore, - useState, - useMutators, - useSelector, -} = createContext(store); -``` - -Wrap your app with ``. - -```tsx - - - -``` - -Use `connect()()` higer-order-component to inject props from state into your component. - -```tsx -interface Props { - name: string; - punctuation: '.' | ',' | '!', -} -const Demo: React.FC = ({ name, punctuation }) => -
    Hello, {name}{punctuation}
    ; - -const store = createStore({ userName: 'John' }); -const { Provider, connect } = createContext(store); - -const mapStateToProps = ({ userName }) => ({ name: userName }); -const DemoConnected = connect(mapStateToProps)(Demo); - - - - -``` - -`useStore` React hook will fetch the `store` object from the context. - -```tsx -const Demo = () => { - const store = useStore(); - return
    {store.get().isDarkMode ? '🌑' : '☀️'}
    ; -}; -``` - -If you want your component to always re-render when the state changes use `useState` React hook. - -```tsx -const Demo = () => { - const { isDarkMode } = useState(); - return
    {isDarkMode ? '🌑' : '☀️'}
    ; -}; -``` - -For `useSelector` React hook you specify a selector function, which will pick specific -data from the state. *Your component will update only when that specific part of the state changes.* - -```tsx -const selector = state => state.isDarkMode; -const Demo = () => { - const isDarkMode = useSelector(selector); - return
    {isDarkMode ? '🌑' : '☀️'}
    ; -}; -``` - -As an optional second argument for `useSelector` you can provide a `comparator` function, which -compares currently selected value with the previous and your component will re-render only if -`comparator` returns `true`. By default, it simply uses tripple equals `===` comparison. - -``` -useSelector(selector, comparator?) -``` - -Access state mutators by `useMutators` React hook. - -```tsx -const Demo = () => { - const { isDarkMode } = useState(); - const { setDarkMode } = useMutators(); - return ( - <> -
    {isDarkMode ? '🌑' : '☀️'}
    - - - - ); -}; -``` diff --git a/src/plugins/kibana_utils/docs/store/redux.md b/src/plugins/kibana_utils/docs/store/redux.md deleted file mode 100644 index 23be76f35b36ef..00000000000000 --- a/src/plugins/kibana_utils/docs/store/redux.md +++ /dev/null @@ -1,19 +0,0 @@ -# Redux - -Internally `createStore()` uses Redux to manage the state. When you call `store.get()` -it is actually calling the Redux `.getState()` method. When you execute a mutation -it is actually dispatching a Redux action. - -You can access Redux *store* using `.redux`. - -```ts -store.redux; -``` - -But you should never need it, if you think you do, consult with Kibana App Architecture team. - -We use Redux internally for 3 main reasons: - -- We can reuse `react-redux` library to easily connect state containers to React. -- We can reuse Redux devtools. -- We can reuse battle-tested Redux library and action/reducer paradigm. diff --git a/src/plugins/kibana_utils/public/index.test.ts b/src/plugins/kibana_utils/public/index.test.ts index 0e2a4acf15f048..27c4d6c1c06e91 100644 --- a/src/plugins/kibana_utils/public/index.test.ts +++ b/src/plugins/kibana_utils/public/index.test.ts @@ -17,9 +17,9 @@ * under the License. */ -import { createStore, createContext } from '.'; +import { createStateContainer, createStateContainerReactHelpers } from '.'; test('exports store methods', () => { - expect(typeof createStore).toBe('function'); - expect(typeof createContext).toBe('function'); + expect(typeof createStateContainer).toBe('function'); + expect(typeof createStateContainerReactHelpers).toBe('function'); }); diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 04845c72cb755c..3f5aeebac54d84 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -19,11 +19,13 @@ export * from './core'; export * from './errors'; -export * from './store'; -export * from './parse'; -export * from './resize_checker'; -export * from './render_complete'; -export * from './store'; export * from './errors'; export * from './field_mapping'; +export * from './parse'; +export * from './render_complete'; +export * from './resize_checker'; +export * from './state_containers'; export * from './storage'; +export * from './storage/hashed_item_store'; +export * from './state_management/state_hash'; +export * from './state_management/url'; diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts new file mode 100644 index 00000000000000..9165181299a90a --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts @@ -0,0 +1,303 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createStateContainer } from './create_state_container'; + +const create = (state: S, transitions: T = {} as T) => { + const pureTransitions = { + set: () => (newState: S) => newState, + ...transitions, + }; + const store = createStateContainer(state, pureTransitions); + return { store, mutators: store.transitions }; +}; + +test('can create store', () => { + const { store } = create({}); + expect(store).toMatchObject({ + getState: expect.any(Function), + state$: expect.any(Object), + transitions: expect.any(Object), + dispatch: expect.any(Function), + subscribe: expect.any(Function), + replaceReducer: expect.any(Function), + addMiddleware: expect.any(Function), + }); +}); + +test('can set default state', () => { + const defaultState = { + foo: 'bar', + }; + const { store } = create(defaultState); + expect(store.get()).toEqual(defaultState); + expect(store.getState()).toEqual(defaultState); +}); + +test('can set state', () => { + const defaultState = { + foo: 'bar', + }; + const newState = { + foo: 'baz', + }; + const { store, mutators } = create(defaultState); + + mutators.set(newState); + + expect(store.get()).toEqual(newState); + expect(store.getState()).toEqual(newState); +}); + +test('does not shallow merge states', () => { + const defaultState = { + foo: 'bar', + }; + const newState = { + foo2: 'baz', + }; + const { store, mutators } = create(defaultState); + + mutators.set(newState as any); + + expect(store.get()).toEqual(newState); + expect(store.getState()).toEqual(newState); +}); + +test('can subscribe and unsubscribe to state changes', () => { + const { store, mutators } = create({}); + const spy = jest.fn(); + const subscription = store.state$.subscribe(spy); + mutators.set({ a: 1 }); + mutators.set({ a: 2 }); + subscription.unsubscribe(); + mutators.set({ a: 3 }); + + expect(spy).toHaveBeenCalledTimes(2); + expect(spy.mock.calls[0][0]).toEqual({ a: 1 }); + expect(spy.mock.calls[1][0]).toEqual({ a: 2 }); +}); + +test('multiple subscribers can subscribe', () => { + const { store, mutators } = create({}); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const subscription1 = store.state$.subscribe(spy1); + const subscription2 = store.state$.subscribe(spy2); + mutators.set({ a: 1 }); + subscription1.unsubscribe(); + mutators.set({ a: 2 }); + subscription2.unsubscribe(); + mutators.set({ a: 3 }); + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(2); + expect(spy1.mock.calls[0][0]).toEqual({ a: 1 }); + expect(spy2.mock.calls[0][0]).toEqual({ a: 1 }); + expect(spy2.mock.calls[1][0]).toEqual({ a: 2 }); +}); + +test('creates impure mutators from pure mutators', () => { + const { mutators } = create( + {}, + { + setFoo: () => (bar: any) => ({ foo: bar }), + } + ); + + expect(typeof mutators.setFoo).toBe('function'); +}); + +test('mutators can update state', () => { + const { store, mutators } = create( + { + value: 0, + foo: 'bar', + }, + { + add: (state: any) => (increment: any) => ({ ...state, value: state.value + increment }), + setFoo: (state: any) => (bar: any) => ({ ...state, foo: bar }), + } + ); + + expect(store.get()).toEqual({ + value: 0, + foo: 'bar', + }); + + mutators.add(11); + mutators.setFoo('baz'); + + expect(store.get()).toEqual({ + value: 11, + foo: 'baz', + }); + + mutators.add(-20); + mutators.setFoo('bazooka'); + + expect(store.get()).toEqual({ + value: -9, + foo: 'bazooka', + }); +}); + +test('mutators methods are not bound', () => { + const { store, mutators } = create( + { value: -3 }, + { + add: (state: { value: number }) => (increment: number) => ({ + ...state, + value: state.value + increment, + }), + } + ); + + expect(store.get()).toEqual({ value: -3 }); + mutators.add(4); + expect(store.get()).toEqual({ value: 1 }); +}); + +test('created mutators are saved in store object', () => { + const { store, mutators } = create( + { value: -3 }, + { + add: (state: { value: number }) => (increment: number) => ({ + ...state, + value: state.value + increment, + }), + } + ); + + expect(typeof store.transitions.add).toBe('function'); + mutators.add(5); + expect(store.get()).toEqual({ value: 2 }); +}); + +test('throws when state is modified inline - 1', () => { + const container = createStateContainer({ a: 'b' }, {}); + + let error: TypeError | null = null; + try { + (container.get().a as any) = 'c'; + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(TypeError); +}); + +test('throws when state is modified inline - 2', () => { + const container = createStateContainer({ a: 'b' }, {}); + + let error: TypeError | null = null; + try { + (container.getState().a as any) = 'c'; + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(TypeError); +}); + +test('throws when state is modified inline in subscription', done => { + const container = createStateContainer({ a: 'b' }, { set: () => (newState: any) => newState }); + + container.subscribe(value => { + let error: TypeError | null = null; + try { + (value.a as any) = 'd'; + } catch (err) { + error = err; + } + expect(error).toBeInstanceOf(TypeError); + done(); + }); + container.transitions.set({ a: 'c' }); +}); + +describe('selectors', () => { + test('can specify no selectors, or can skip them', () => { + createStateContainer({}, {}); + createStateContainer({}, {}, {}); + }); + + test('selector object is available on .selectors key', () => { + const container1 = createStateContainer({}, {}, {}); + const container2 = createStateContainer({}, {}, { foo: () => () => 123 }); + const container3 = createStateContainer({}, {}, { bar: () => () => 1, baz: () => () => 1 }); + + expect(Object.keys(container1.selectors).sort()).toEqual([]); + expect(Object.keys(container2.selectors).sort()).toEqual(['foo']); + expect(Object.keys(container3.selectors).sort()).toEqual(['bar', 'baz']); + }); + + test('selector without arguments returns correct state slice', () => { + const container = createStateContainer( + { name: 'Oleg' }, + { + changeName: (state: { name: string }) => (name: string) => ({ ...state, name }), + }, + { getName: (state: { name: string }) => () => state.name } + ); + + expect(container.selectors.getName()).toBe('Oleg'); + container.transitions.changeName('Britney'); + expect(container.selectors.getName()).toBe('Britney'); + }); + + test('selector can accept an argument', () => { + const container = createStateContainer( + { + users: { + 1: { + name: 'Darth', + }, + }, + }, + {}, + { + getUser: (state: any) => (id: number) => state.users[id], + } + ); + + expect(container.selectors.getUser(1)).toEqual({ name: 'Darth' }); + expect(container.selectors.getUser(2)).toBe(undefined); + }); + + test('selector can accept multiple arguments', () => { + const container = createStateContainer( + { + users: { + 5: { + name: 'Darth', + surname: 'Vader', + }, + }, + }, + {}, + { + getName: (state: any) => (id: number, which: 'name' | 'surname') => state.users[id][which], + } + ); + + expect(container.selectors.getName(5, 'name')).toEqual('Darth'); + expect(container.selectors.getName(5, 'surname')).toEqual('Vader'); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container.ts new file mode 100644 index 00000000000000..1ef4a1c0128170 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container.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 { BehaviorSubject } from 'rxjs'; +import { skip } from 'rxjs/operators'; +import { RecursiveReadonly } from '@kbn/utility-types'; +import { + PureTransitionsToTransitions, + PureTransition, + ReduxLikeStateContainer, + PureSelectorsToSelectors, +} from './types'; + +const $$observable = (typeof Symbol === 'function' && (Symbol as any).observable) || '@@observable'; + +const freeze: (value: T) => RecursiveReadonly = + process.env.NODE_ENV !== 'production' + ? (value: T): RecursiveReadonly => { + if (!value) return value as RecursiveReadonly; + if (value instanceof Array) return value as RecursiveReadonly; + if (typeof value === 'object') return Object.freeze({ ...value }) as RecursiveReadonly; + else return value as RecursiveReadonly; + } + : (value: T) => value as RecursiveReadonly; + +export const createStateContainer = < + State, + PureTransitions extends object, + PureSelectors extends object = {} +>( + defaultState: State, + pureTransitions: PureTransitions, + pureSelectors: PureSelectors = {} as PureSelectors +): ReduxLikeStateContainer => { + const data$ = new BehaviorSubject>(freeze(defaultState)); + const state$ = data$.pipe(skip(1)); + const get = () => data$.getValue(); + const container: ReduxLikeStateContainer = { + get, + state$, + getState: () => data$.getValue(), + set: (state: State) => { + data$.next(freeze(state)); + }, + reducer: (state, action) => { + const pureTransition = (pureTransitions as Record>)[ + action.type + ]; + return pureTransition ? freeze(pureTransition(state)(...action.args)) : state; + }, + replaceReducer: nextReducer => (container.reducer = nextReducer), + dispatch: action => data$.next(container.reducer(get(), action)), + transitions: Object.keys(pureTransitions).reduce>( + (acc, type) => ({ ...acc, [type]: (...args: any) => container.dispatch({ type, args }) }), + {} as PureTransitionsToTransitions + ), + selectors: Object.keys(pureSelectors).reduce>( + (acc, selector) => ({ + ...acc, + [selector]: (...args: any) => (pureSelectors as any)[selector](get())(...args), + }), + {} as PureSelectorsToSelectors + ), + addMiddleware: middleware => + (container.dispatch = middleware(container as any)(container.dispatch)), + subscribe: (listener: (state: RecursiveReadonly) => void) => { + const subscription = state$.subscribe(listener); + return () => subscription.unsubscribe(); + }, + [$$observable]: state$, + }; + return container; +}; diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx new file mode 100644 index 00000000000000..8f5810f3e147d3 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx @@ -0,0 +1,416 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 React from 'react'; +import * as ReactDOM from 'react-dom'; +import { act, Simulate } from 'react-dom/test-utils'; +import { createStateContainer } from './create_state_container'; +import { createStateContainerReactHelpers } from './create_state_container_react_helpers'; + +const create = (state: S, transitions: T = {} as T) => { + const pureTransitions = { + set: () => (newState: S) => newState, + ...transitions, + }; + const store = createStateContainer(state, pureTransitions); + return { store, mutators: store.transitions }; +}; + +let container: HTMLDivElement | null; + +beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container!); + container = null; +}); + +test('can create React context', () => { + const context = createStateContainerReactHelpers(); + + expect(context).toMatchObject({ + Provider: expect.any(Object), + Consumer: expect.any(Object), + connect: expect.any(Function), + context: expect.any(Object), + }); +}); + +test(' passes state to ', () => { + const { store } = create({ hello: 'world' }); + const { Provider, Consumer } = createStateContainerReactHelpers(); + + ReactDOM.render( + + {(s: typeof store) => s.get().hello} + , + container + ); + + expect(container!.innerHTML).toBe('world'); +}); + +interface State1 { + hello: string; +} + +interface Props1 { + message: string; + stop: '.' | '!' | '?'; +} + +test(' passes state to connect()()', () => { + const { store } = create({ hello: 'Bob' }); + const { Provider, connect } = createStateContainerReactHelpers(); + + const Demo: React.FC = ({ message, stop }) => ( + <> + {message} + {stop} + + ); + const mergeProps = ({ hello }: State1) => ({ message: hello }); + const DemoConnected = connect(mergeProps)(Demo); + + ReactDOM.render( + + + , + container + ); + + expect(container!.innerHTML).toBe('Bob?'); +}); + +test('context receives Redux store', () => { + const { store } = create({ foo: 'bar' }); + const { Provider, context } = createStateContainerReactHelpers(); + + ReactDOM.render( + /* eslint-disable no-shadow */ + + {store => store.get().foo} + , + /* eslint-enable no-shadow */ + container + ); + + expect(container!.innerHTML).toBe('bar'); +}); + +xtest('can use multiple stores in one React app', () => {}); + +describe('hooks', () => { + describe('useStore', () => { + test('can select store using useStore hook', () => { + const { store } = create({ foo: 'bar' }); + const { Provider, useContainer } = createStateContainerReactHelpers(); + const Demo: React.FC<{}> = () => { + // eslint-disable-next-line no-shadow + const store = useContainer(); + return <>{store.get().foo}; + }; + + ReactDOM.render( + + + , + container + ); + + expect(container!.innerHTML).toBe('bar'); + }); + }); + + describe('useState', () => { + test('can select state using useState hook', () => { + const { store } = create({ foo: 'qux' }); + const { Provider, useState } = createStateContainerReactHelpers(); + const Demo: React.FC<{}> = () => { + const { foo } = useState(); + return <>{foo}; + }; + + ReactDOM.render( + + + , + container + ); + + expect(container!.innerHTML).toBe('qux'); + }); + + test('re-renders when state changes', () => { + const { + store, + mutators: { setFoo }, + } = create( + { foo: 'bar' }, + { + setFoo: (state: { foo: string }) => (foo: string) => ({ ...state, foo }), + } + ); + const { Provider, useState } = createStateContainerReactHelpers(); + const Demo: React.FC<{}> = () => { + const { foo } = useState(); + return <>{foo}; + }; + + ReactDOM.render( + + + , + container + ); + + expect(container!.innerHTML).toBe('bar'); + act(() => { + setFoo('baz'); + }); + expect(container!.innerHTML).toBe('baz'); + }); + }); + + describe('useTransitions', () => { + test('useTransitions hook returns mutations that can update state', () => { + const { store } = create< + { + cnt: number; + }, + any + >( + { + cnt: 0, + }, + { + increment: (state: { cnt: number }) => (value: number) => ({ + ...state, + cnt: state.cnt + value, + }), + } + ); + + const { Provider, useState, useTransitions } = createStateContainerReactHelpers< + typeof store + >(); + const Demo: React.FC<{}> = () => { + const { cnt } = useState(); + const { increment } = useTransitions(); + return ( + <> + {cnt} + + + ); + }; + + ReactDOM.render( + + + , + container + ); + + expect(container!.querySelector('strong')!.innerHTML).toBe('0'); + act(() => { + Simulate.click(container!.querySelector('button')!, {}); + }); + expect(container!.querySelector('strong')!.innerHTML).toBe('10'); + act(() => { + Simulate.click(container!.querySelector('button')!, {}); + }); + expect(container!.querySelector('strong')!.innerHTML).toBe('20'); + }); + }); + + describe('useSelector', () => { + test('can select deeply nested value', () => { + const { store } = create({ + foo: { + bar: { + baz: 'qux', + }, + }, + }); + const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; + const { Provider, useSelector } = createStateContainerReactHelpers(); + const Demo: React.FC<{}> = () => { + const value = useSelector(selector); + return <>{value}; + }; + + ReactDOM.render( + + + , + container + ); + + expect(container!.innerHTML).toBe('qux'); + }); + + test('re-renders when state changes', () => { + const { store, mutators } = create({ + foo: { + bar: { + baz: 'qux', + }, + }, + }); + const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; + const { Provider, useSelector } = createStateContainerReactHelpers(); + const Demo: React.FC<{}> = () => { + const value = useSelector(selector); + return <>{value}; + }; + + ReactDOM.render( + + + , + container + ); + + expect(container!.innerHTML).toBe('qux'); + act(() => { + mutators.set({ + foo: { + bar: { + baz: 'quux', + }, + }, + }); + }); + expect(container!.innerHTML).toBe('quux'); + }); + + test("re-renders only when selector's result changes", async () => { + const { store, mutators } = create({ a: 'b', foo: 'bar' }); + const selector = (state: { foo: string }) => state.foo; + const { Provider, useSelector } = createStateContainerReactHelpers(); + + let cnt = 0; + const Demo: React.FC<{}> = () => { + cnt++; + const value = useSelector(selector); + return <>{value}; + }; + ReactDOM.render( + + + , + container + ); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + + act(() => { + mutators.set({ a: 'c', foo: 'bar' }); + }); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + + act(() => { + mutators.set({ a: 'd', foo: 'bar 2' }); + }); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(2); + }); + + test('does not re-render on same shape object', async () => { + const { store, mutators } = create({ foo: { bar: 'baz' } }); + const selector = (state: { foo: any }) => state.foo; + const { Provider, useSelector } = createStateContainerReactHelpers(); + + let cnt = 0; + const Demo: React.FC<{}> = () => { + cnt++; + const value = useSelector(selector); + return <>{JSON.stringify(value)}; + }; + ReactDOM.render( + + + , + container + ); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + + act(() => { + mutators.set({ foo: { bar: 'baz' } }); + }); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + + act(() => { + mutators.set({ foo: { bar: 'qux' } }); + }); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(2); + }); + + test('can set custom comparator function to prevent re-renders on deep equality', async () => { + const { store, mutators } = create( + { foo: { bar: 'baz' } }, + { + set: () => (newState: { foo: { bar: string } }) => newState, + } + ); + const selector = (state: { foo: any }) => state.foo; + const comparator = (prev: any, curr: any) => JSON.stringify(prev) === JSON.stringify(curr); + const { Provider, useSelector } = createStateContainerReactHelpers(); + + let cnt = 0; + const Demo: React.FC<{}> = () => { + cnt++; + const value = useSelector(selector, comparator); + return <>{JSON.stringify(value)}; + }; + ReactDOM.render( + + + , + container + ); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + + act(() => { + mutators.set({ foo: { bar: 'baz' } }); + }); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + }); + + xtest('unsubscribes when React un-mounts', () => {}); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts new file mode 100644 index 00000000000000..e94165cc483760 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.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 * as React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import defaultComparator from 'fast-deep-equal'; +import { Comparator, Connect, StateContainer, UnboxState } from './types'; + +const { useContext, useLayoutEffect, useRef, createElement: h } = React; + +export const createStateContainerReactHelpers = >() => { + const context = React.createContext(null as any); + + const useContainer = (): Container => useContext(context); + + const useState = (): UnboxState => { + const { state$, get } = useContainer(); + const value = useObservable(state$, get()); + return value; + }; + + const useTransitions = () => useContainer().transitions; + + const useSelector = ( + selector: (state: UnboxState) => Result, + comparator: Comparator = defaultComparator + ): Result => { + const { state$, get } = useContainer(); + const lastValueRef = useRef(get()); + const [value, setValue] = React.useState(() => { + const newValue = selector(get()); + lastValueRef.current = newValue; + return newValue; + }); + useLayoutEffect(() => { + const subscription = state$.subscribe((currentState: UnboxState) => { + const newValue = selector(currentState); + if (!comparator(lastValueRef.current, newValue)) { + lastValueRef.current = newValue; + setValue(newValue); + } + }); + return () => subscription.unsubscribe(); + }, [state$, comparator]); + return value; + }; + + const connect: Connect> = mapStateToProp => component => props => + h(component, { ...useSelector(mapStateToProp), ...props } as any); + + return { + Provider: context.Provider, + Consumer: context.Consumer, + context, + useContainer, + useState, + useTransitions, + useSelector, + connect, + }; +}; diff --git a/src/plugins/kibana_utils/public/state_containers/index.ts b/src/plugins/kibana_utils/public/state_containers/index.ts new file mode 100644 index 00000000000000..43e204ecb79f7b --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/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 './types'; +export * from './create_state_container'; +export * from './create_state_container_react_helpers'; diff --git a/src/plugins/kibana_utils/public/state_containers/types.ts b/src/plugins/kibana_utils/public/state_containers/types.ts new file mode 100644 index 00000000000000..e0a1a18972635e --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/types.ts @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; +import { Ensure, RecursiveReadonly } from '@kbn/utility-types'; + +export interface TransitionDescription { + type: Type; + args: Args; +} +export type Transition = (...args: Args) => State; +export type PureTransition = ( + state: RecursiveReadonly +) => Transition; +export type EnsurePureTransition = Ensure>; +export type PureTransitionToTransition> = ReturnType; +export type PureTransitionsToTransitions = { + [K in keyof T]: PureTransitionToTransition>; +}; + +export interface BaseStateContainer { + get: () => RecursiveReadonly; + set: (state: State) => void; + state$: Observable>; +} + +export interface StateContainer< + State, + PureTransitions extends object, + PureSelectors extends object = {} +> extends BaseStateContainer { + transitions: Readonly>; + selectors: Readonly>; +} + +export interface ReduxLikeStateContainer< + State, + PureTransitions extends object, + PureSelectors extends object = {} +> extends StateContainer { + getState: () => RecursiveReadonly; + reducer: Reducer>; + replaceReducer: (nextReducer: Reducer>) => void; + dispatch: (action: TransitionDescription) => void; + addMiddleware: (middleware: Middleware>) => void; + subscribe: (listener: (state: RecursiveReadonly) => void) => () => void; +} + +export type Dispatch = (action: T) => void; + +export type Middleware = ( + store: Pick, 'getState' | 'dispatch'> +) => ( + next: (action: TransitionDescription) => TransitionDescription | any +) => Dispatch; + +export type Reducer = (state: State, action: TransitionDescription) => State; + +export type UnboxState< + Container extends StateContainer +> = Container extends StateContainer ? T : never; +export type UnboxTransitions< + Container extends StateContainer +> = Container extends StateContainer ? T : never; + +export type Selector = (...args: Args) => Result; +export type PureSelector = ( + state: State +) => Selector; +export type EnsurePureSelector = Ensure>; +export type PureSelectorToSelector> = ReturnType< + EnsurePureSelector +>; +export type PureSelectorsToSelectors = { + [K in keyof T]: PureSelectorToSelector>; +}; + +export type Comparator = (previous: Result, current: Result) => boolean; + +export type MapStateToProps = (state: State) => StateProps; +export type Connect = ( + mapStateToProp: MapStateToProps> +) => (component: React.ComponentType) => React.FC>; diff --git a/src/plugins/kibana_utils/public/state_management/state_hash/index.ts b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts new file mode 100644 index 00000000000000..0e52c4c55872d2 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/state_hash/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 './state_hash'; diff --git a/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts new file mode 100644 index 00000000000000..cccb74acaf1e54 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.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 { encode as encodeRison } from 'rison-node'; +import { mockStorage } from '../../storage/hashed_item_store/mock'; +import { createStateHash, isStateHash } from './state_hash'; + +describe('stateHash', () => { + beforeEach(() => { + mockStorage.clear(); + }); + + describe('#createStateHash', () => { + it('returns a hash', () => { + const json = JSON.stringify({ a: 'a' }); + const hash = createStateHash(json); + expect(isStateHash(hash)).toBe(true); + }); + + it('returns the same hash for the same input', () => { + const json = JSON.stringify({ a: 'a' }); + const hash1 = createStateHash(json); + const hash2 = createStateHash(json); + expect(hash1).toEqual(hash2); + }); + + it('returns a different hash for different input', () => { + const json1 = JSON.stringify({ a: 'a' }); + const hash1 = createStateHash(json1); + + const json2 = JSON.stringify({ a: 'b' }); + const hash2 = createStateHash(json2); + expect(hash1).not.toEqual(hash2); + }); + }); + + describe('#isStateHash', () => { + it('returns true for values created using #createStateHash', () => { + const json = JSON.stringify({ a: 'a' }); + const hash = createStateHash(json); + expect(isStateHash(hash)).toBe(true); + }); + + it('returns false for values not created using #createStateHash', () => { + const json = JSON.stringify({ a: 'a' }); + expect(isStateHash(json)).toBe(false); + }); + + it('returns false for RISON', () => { + // We're storing RISON in the URL, so let's test against this specifically. + const rison = encodeRison({ a: 'a' }); + expect(isStateHash(rison)).toBe(false); + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts new file mode 100644 index 00000000000000..a3eb5272b112da --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.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 { Sha256 } from '../../../../../core/public/utils'; +import { hashedItemStore } from '../../storage/hashed_item_store'; + +// This prefix is used to identify hash strings that have been encoded in the URL. +const HASH_PREFIX = 'h@'; + +export function createStateHash( + json: string, + existingJsonProvider?: (hash: string) => string | null // TODO: temp while state.js relies on this in tests +) { + if (typeof json !== 'string') { + throw new Error('createHash only accepts strings (JSON).'); + } + + const hash = new Sha256().update(json, 'utf8').digest('hex'); + + let shortenedHash; + + // Shorten the hash to at minimum 7 characters. We just need to make sure that it either: + // a) hasn't been used yet + // b) or has been used already, but with the JSON we're currently hashing. + for (let i = 7; i < hash.length; i++) { + shortenedHash = hash.slice(0, i); + const existingJson = existingJsonProvider + ? existingJsonProvider(shortenedHash) + : hashedItemStore.getItem(shortenedHash); + if (existingJson === null || existingJson === json) break; + } + + return `${HASH_PREFIX}${shortenedHash}`; +} + +export function isStateHash(str: string) { + return String(str).indexOf(HASH_PREFIX) === 0; +} diff --git a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts new file mode 100644 index 00000000000000..a85158acddefd5 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts @@ -0,0 +1,286 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mockStorage } from '../../storage/hashed_item_store/mock'; +import { HashedItemStore } from '../../storage/hashed_item_store'; +import { hashUrl, unhashUrl } from './hash_unhash_url'; + +describe('hash unhash url', () => { + beforeEach(() => { + mockStorage.clear(); + mockStorage.setStubbedSizeLimit(5000000); + }); + + describe('hash url', () => { + describe('does nothing', () => { + it('if missing input', () => { + expect(() => { + // @ts-ignore + hashUrl(); + }).not.toThrowError(); + }); + + it('if url is empty', () => { + const url = ''; + expect(hashUrl(url)).toBe(url); + }); + + it('if just a host and port', () => { + const url = 'https://localhost:5601'; + expect(hashUrl(url)).toBe(url); + }); + + it('if just a path', () => { + const url = 'https://localhost:5601/app/kibana'; + expect(hashUrl(url)).toBe(url); + }); + + it('if just a path and query', () => { + const url = 'https://localhost:5601/app/kibana?foo=bar'; + expect(hashUrl(url)).toBe(url); + }); + + it('if empty hash with query', () => { + const url = 'https://localhost:5601/app/kibana?foo=bar#'; + expect(hashUrl(url)).toBe(url); + }); + + it('if query parameter matches and there is no hash', () => { + const url = 'https://localhost:5601/app/kibana?testParam=(yes:!t)'; + expect(hashUrl(url)).toBe(url); + }); + + it(`if query parameter matches and it's before the hash`, () => { + const url = 'https://localhost:5601/app/kibana?testParam=(yes:!t)'; + expect(hashUrl(url)).toBe(url); + }); + + it('if empty hash without query', () => { + const url = 'https://localhost:5601/app/kibana#'; + expect(hashUrl(url)).toBe(url); + }); + + it('if hash is just a path', () => { + const url = 'https://localhost:5601/app/kibana#/discover'; + expect(hashUrl(url)).toBe(url); + }); + + it('if hash does not have matching query string vals', () => { + const url = 'https://localhost:5601/app/kibana#/discover?foo=bar'; + expect(hashUrl(url)).toBe(url); + }); + }); + + describe('replaces expanded state with hash', () => { + it('if uses single state param', () => { + const stateParamKey = '_g'; + const stateParamValue = '(yes:!t)'; + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey}=${stateParamValue}`; + const result = hashUrl(url); + expect(result).toMatchInlineSnapshot( + `"https://localhost:5601/app/kibana#/discover?foo=bar&_g=h@4e60e02"` + ); + expect(mockStorage.getItem('kbn.hashedItemsIndex.v1')).toBeTruthy(); + expect(mockStorage.getItem('h@4e60e02')).toEqual(JSON.stringify({ yes: true })); + }); + + it('if uses multiple states params', () => { + const stateParamKey1 = '_g'; + const stateParamValue1 = '(yes:!t)'; + const stateParamKey2 = '_a'; + const stateParamValue2 = '(yes:!f)'; + const stateParamKey3 = '_b'; + const stateParamValue3 = '(yes:!f)'; + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValue1}&${stateParamKey2}=${stateParamValue2}&${stateParamKey3}=${stateParamValue3}`; + const result = hashUrl(url); + expect(result).toMatchInlineSnapshot( + `"https://localhost:5601/app/kibana#/discover?foo=bar&_g=h@4e60e02&_a=h@61fa078&_b=(yes:!f)"` + ); + expect(mockStorage.getItem('h@4e60e02')).toEqual(JSON.stringify({ yes: true })); + expect(mockStorage.getItem('h@61fa078')).toEqual(JSON.stringify({ yes: false })); + if (!HashedItemStore.PERSISTED_INDEX_KEY) { + // This is very brittle and depends upon HashedItemStore implementation details, + // so let's protect ourselves from accidentally breaking this test. + throw new Error('Missing HashedItemStore.PERSISTED_INDEX_KEY'); + } + expect(mockStorage.getItem(HashedItemStore.PERSISTED_INDEX_KEY)).toBeTruthy(); + expect(mockStorage.length).toBe(3); + }); + + it('hashes only whitelisted properties', () => { + const stateParamKey1 = '_g'; + const stateParamValue1 = '(yes:!t)'; + const stateParamKey2 = '_a'; + const stateParamValue2 = '(yes:!f)'; + const stateParamKey3 = '_someother'; + const stateParamValue3 = '(yes:!f)'; + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValue1}&${stateParamKey2}=${stateParamValue2}&${stateParamKey3}=${stateParamValue3}`; + const result = hashUrl(url); + expect(result).toMatchInlineSnapshot( + `"https://localhost:5601/app/kibana#/discover?foo=bar&_g=h@4e60e02&_a=h@61fa078&_someother=(yes:!f)"` + ); + + expect(mockStorage.length).toBe(3); // 2 hashes + HashedItemStoreSingleton.PERSISTED_INDEX_KEY + }); + }); + + it('throws error if unable to hash url', () => { + const stateParamKey1 = '_g'; + const stateParamValue1 = '(yes:!t)'; + mockStorage.setStubbedSizeLimit(1); + + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValue1}`; + expect(() => hashUrl(url)).toThrowError(); + }); + }); + + describe('unhash url', () => { + describe('does nothing', () => { + it('if missing input', () => { + expect(() => { + // @ts-ignore + }).not.toThrowError(); + }); + + it('if just a host and port', () => { + const url = 'https://localhost:5601'; + expect(unhashUrl(url)).toBe(url); + }); + + it('if just a path', () => { + const url = 'https://localhost:5601/app/kibana'; + expect(unhashUrl(url)).toBe(url); + }); + + it('if just a path and query', () => { + const url = 'https://localhost:5601/app/kibana?foo=bar'; + expect(unhashUrl(url)).toBe(url); + }); + + it('if empty hash with query', () => { + const url = 'https://localhost:5601/app/kibana?foo=bar#'; + expect(unhashUrl(url)).toBe(url); + }); + + it('if empty hash without query', () => { + const url = 'https://localhost:5601/app/kibana#'; + expect(unhashUrl(url)).toBe(url); + }); + + it('if hash is just a path', () => { + const url = 'https://localhost:5601/app/kibana#/discover'; + expect(unhashUrl(url)).toBe(url); + }); + + it('if hash does not have matching query string vals', () => { + const url = 'https://localhost:5601/app/kibana#/discover?foo=bar'; + expect(unhashUrl(url)).toBe(url); + }); + + it("if hash has matching query, but it isn't hashed", () => { + const stateParamKey = '_g'; + const stateParamValue = '(yes:!t)'; + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey}=${stateParamValue}`; + expect(unhashUrl(url)).toBe(url); + }); + }); + + describe('replaces expanded state with hash', () => { + it('if uses single state param', () => { + const stateParamKey = '_g'; + const stateParamValueHashed = 'h@4e60e02'; + const state = { yes: true }; + mockStorage.setItem(stateParamValueHashed, JSON.stringify(state)); + + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey}=${stateParamValueHashed}`; + const result = unhashUrl(url); + expect(result).toMatchInlineSnapshot( + `"https://localhost:5601/app/kibana#/discover?foo=bar&_g=(yes:!t)"` + ); + }); + + it('if uses multiple state param', () => { + const stateParamKey1 = '_g'; + const stateParamValueHashed1 = 'h@4e60e02'; + const state1 = { yes: true }; + + const stateParamKey2 = '_a'; + const stateParamValueHashed2 = 'h@61fa078'; + const state2 = { yes: false }; + + mockStorage.setItem(stateParamValueHashed1, JSON.stringify(state1)); + mockStorage.setItem(stateParamValueHashed2, JSON.stringify(state2)); + + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValueHashed1}&${stateParamKey2}=${stateParamValueHashed2}`; + const result = unhashUrl(url); + expect(result).toMatchInlineSnapshot( + `"https://localhost:5601/app/kibana#/discover?foo=bar&_g=(yes:!t)&_a=(yes:!f)"` + ); + }); + + it('unhashes only whitelisted properties', () => { + const stateParamKey1 = '_g'; + const stateParamValueHashed1 = 'h@4e60e02'; + const state1 = { yes: true }; + + const stateParamKey2 = '_a'; + const stateParamValueHashed2 = 'h@61fa078'; + const state2 = { yes: false }; + + const stateParamKey3 = '_someother'; + const stateParamValueHashed3 = 'h@61fa078'; + const state3 = { yes: false }; + + mockStorage.setItem(stateParamValueHashed1, JSON.stringify(state1)); + mockStorage.setItem(stateParamValueHashed2, JSON.stringify(state2)); + mockStorage.setItem(stateParamValueHashed3, JSON.stringify(state3)); + + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValueHashed1}&${stateParamKey2}=${stateParamValueHashed2}&${stateParamKey3}=${stateParamValueHashed3}`; + const result = unhashUrl(url); + expect(result).toMatchInlineSnapshot( + `"https://localhost:5601/app/kibana#/discover?foo=bar&_g=(yes:!t)&_a=(yes:!f)&_someother=h@61fa078"` + ); + }); + }); + + it('throws error if unable to restore the url', () => { + const stateParamKey1 = '_g'; + const stateParamValueHashed1 = 'h@4e60e02'; + + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValueHashed1}`; + expect(() => unhashUrl(url)).toThrowErrorMatchingInlineSnapshot( + `"Unable to completely restore the URL, be sure to use the share functionality."` + ); + }); + }); + + describe('hash unhash url integration', () => { + it('hashing and unhashing url should produce the same result', () => { + const stateParamKey1 = '_g'; + const stateParamValue1 = '(yes:!t)'; + const stateParamKey2 = '_a'; + const stateParamValue2 = '(yes:!f)'; + const stateParamKey3 = '_someother'; + const stateParamValue3 = '(yes:!f)'; + const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValue1}&${stateParamKey2}=${stateParamValue2}&${stateParamKey3}=${stateParamValue3}`; + const result = unhashUrl(hashUrl(url)); + expect(url).toEqual(result); + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts new file mode 100644 index 00000000000000..872e7953f938bb --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts @@ -0,0 +1,157 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 rison, { RisonObject } from 'rison-node'; +import { stringify as stringifyQueryString } from 'querystring'; +import encodeUriQuery from 'encode-uri-query'; +import { format as formatUrl, parse as parseUrl } from 'url'; +import { hashedItemStore } from '../../storage/hashed_item_store'; +import { createStateHash, isStateHash } from '../state_hash'; + +export type IParsedUrlQuery = Record; + +interface IUrlQueryMapperOptions { + hashableParams: string[]; +} +export type IUrlQueryReplacerOptions = IUrlQueryMapperOptions; + +export const unhashQuery = createQueryMapper(stateHashToRisonState); +export const hashQuery = createQueryMapper(risonStateToStateHash); + +export const unhashUrl = createQueryReplacer(unhashQuery); +export const hashUrl = createQueryReplacer(hashQuery); + +// naive hack, but this allows to decouple these utils from AppState, GlobalState for now +// when removing AppState, GlobalState and migrating to IState containers, +// need to make sure that apps explicitly passing this whitelist to hash +const __HACK_HARDCODED_LEGACY_HASHABLE_PARAMS = ['_g', '_a', '_s']; +function createQueryMapper(queryParamMapper: (q: string) => string | null) { + return ( + query: IParsedUrlQuery, + options: IUrlQueryMapperOptions = { + hashableParams: __HACK_HARDCODED_LEGACY_HASHABLE_PARAMS, + } + ) => + Object.fromEntries( + Object.entries(query || {}).map(([name, value]) => { + if (!options.hashableParams.includes(name)) return [name, value]; + return [name, queryParamMapper(value) || value]; + }) + ); +} + +function createQueryReplacer( + queryMapper: (q: IParsedUrlQuery, options?: IUrlQueryMapperOptions) => IParsedUrlQuery, + options?: IUrlQueryReplacerOptions +) { + return (url: string) => { + if (!url) return url; + + const parsedUrl = parseUrl(url, true); + if (!parsedUrl.hash) return url; + + const appUrl = parsedUrl.hash.slice(1); // trim the # + if (!appUrl) return url; + + const appUrlParsed = parseUrl(appUrl, true); + if (!appUrlParsed.query) return url; + + const changedAppQuery = queryMapper(appUrlParsed.query, options); + + // encodeUriQuery implements the less-aggressive encoding done naturally by + // the browser. We use it to generate the same urls the browser would + const changedAppQueryString = stringifyQueryString(changedAppQuery, undefined, undefined, { + encodeURIComponent: encodeUriQuery, + }); + + return formatUrl({ + ...parsedUrl, + hash: formatUrl({ + pathname: appUrlParsed.pathname, + search: changedAppQueryString, + }), + }); + }; +} + +// TODO: this helper should be merged with or replaced by +// src/legacy/ui/public/state_management/state_storage/hashed_item_store.ts +// maybe to become simplified stateless version +export function retrieveState(stateHash: string): RisonObject { + const json = hashedItemStore.getItem(stateHash); + const throwUnableToRestoreUrlError = () => { + throw new Error( + i18n.translate('kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage', { + defaultMessage: + 'Unable to completely restore the URL, be sure to use the share functionality.', + }) + ); + }; + if (json === null) { + return throwUnableToRestoreUrlError(); + } + try { + return JSON.parse(json); + } catch (e) { + return throwUnableToRestoreUrlError(); + } +} + +// TODO: this helper should be merged with or replaced by +// src/legacy/ui/public/state_management/state_storage/hashed_item_store.ts +// maybe to become simplified stateless version +export function persistState(state: RisonObject): string { + const json = JSON.stringify(state); + const hash = createStateHash(json); + + const isItemSet = hashedItemStore.setItem(hash, json); + if (isItemSet) return hash; + // If we ran out of space trying to persist the state, notify the user. + const message = i18n.translate( + 'kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage', + { + defaultMessage: + 'Kibana is unable to store history items in your session ' + + `because it is full and there don't seem to be items any items safe ` + + 'to delete.\n\n' + + 'This can usually be fixed by moving to a fresh tab, but could ' + + 'be caused by a larger issue. If you are seeing this message regularly, ' + + 'please file an issue at {gitHubIssuesUrl}.', + values: { gitHubIssuesUrl: 'https://github.com/elastic/kibana/issues' }, + } + ); + throw new Error(message); +} + +function stateHashToRisonState(stateHashOrRison: string): string { + if (isStateHash(stateHashOrRison)) { + return rison.encode(retrieveState(stateHashOrRison)); + } + + return stateHashOrRison; +} + +function risonStateToStateHash(stateHashOrRison: string): string | null { + if (isStateHash(stateHashOrRison)) { + return stateHashOrRison; + } + + return persistState(rison.decode(stateHashOrRison) as RisonObject); +} diff --git a/src/plugins/kibana_utils/public/state_management/url/index.ts b/src/plugins/kibana_utils/public/state_management/url/index.ts new file mode 100644 index 00000000000000..30c5696233db73 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/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 './hash_unhash_url'; diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts new file mode 100644 index 00000000000000..f0ff77d5162702 --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts @@ -0,0 +1,426 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { HashedItemStore } from './hashed_item_store'; + +describe('hashedItemStore', () => { + describe('interface', () => { + describe('#constructor', () => { + it('retrieves persisted index from sessionStorage', () => { + const sessionStorage = new StubBrowserStorage(); + const spy = jest.spyOn(sessionStorage, 'getItem'); + + const hashedItemStore = new HashedItemStore(sessionStorage); + (hashedItemStore as any).getIndexedItems(); // trigger retrieving of indexedItems array from HashedItemStore.PERSISTED_INDEX_KEY + expect(spy).toBeCalledWith(HashedItemStore.PERSISTED_INDEX_KEY); + spy.mockReset(); + }); + + it('sorts indexed items by touched property', () => { + const a = { + hash: 'a', + touched: 0, + }; + const b = { + hash: 'b', + touched: 2, + }; + const c = { + hash: 'c', + touched: 1, + }; + const sessionStorage = new StubBrowserStorage(); + if (!HashedItemStore.PERSISTED_INDEX_KEY) { + // This is very brittle and depends upon HashedItemStore implementation details, + // so let's protect ourselves from accidentally breaking this test. + throw new Error('Missing HashedItemStore.PERSISTED_INDEX_KEY'); + } + sessionStorage.setItem(HashedItemStore.PERSISTED_INDEX_KEY, JSON.stringify({ a, b, c })); + + const hashedItemStore = new HashedItemStore(sessionStorage); + expect((hashedItemStore as any).getIndexedItems()).toEqual([a, c, b]); + }); + }); + + describe('#setItem', () => { + describe('if the item exists in sessionStorage', () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + const hash = 'a'; + const item = JSON.stringify({}); + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + }); + + it('persists the item in sessionStorage', () => { + hashedItemStore.setItem(hash, item); + expect(sessionStorage.getItem(hash)).toEqual(item); + }); + + it('returns true', () => { + const result = hashedItemStore.setItem(hash, item); + expect(result).toEqual(true); + }); + }); + + describe(`if the item doesn't exist in sessionStorage`, () => { + describe(`if there's storage space`, () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + const hash = 'a'; + const item = JSON.stringify({}); + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + }); + + it('persists the item in sessionStorage', () => { + hashedItemStore.setItem(hash, item); + expect(sessionStorage.getItem(hash)).toEqual(item); + }); + + it('returns true', () => { + const result = hashedItemStore.setItem(hash, item); + expect(result).toEqual(true); + }); + }); + + describe(`if there isn't storage space`, () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + let storageSizeLimit: number; + const hash = 'a'; + const item = JSON.stringify({}); + + function setItemLater(_hash: string, _item: string) { + // Move time forward, so this item will be "touched" most recently. + jest.advanceTimersByTime(1); + return hashedItemStore.setItem(_hash, _item); + } + + beforeEach(() => { + // Control time. + jest.useFakeTimers(); + + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + + // Add some items that will be removed. + setItemLater('b', item); + + // Do this a little later so that this item is newer. + setItemLater('c', item); + + // Cap the storage at its current size. + storageSizeLimit = sessionStorage.getStubbedSize(); + sessionStorage.setStubbedSizeLimit(storageSizeLimit); + }); + + afterEach(() => { + // Stop controlling time. + jest.useRealTimers(); + }); + + describe('and the item will fit', () => { + it('removes older items until the new item fits', () => { + setItemLater(hash, item); + expect(sessionStorage.getItem('b')).toEqual(null); + expect(sessionStorage.getItem('c')).toEqual(item); + }); + + it('persists the item in sessionStorage', () => { + setItemLater(hash, item); + expect(sessionStorage.getItem(hash)).toEqual(item); + }); + + it('returns true', () => { + const result = setItemLater(hash, item); + expect(result).toEqual(true); + }); + }); + + describe(`and the item won't fit`, () => { + let itemTooBigToFit: string; + + beforeEach(() => { + // Make sure the item is longer than the storage size limit. + itemTooBigToFit = ''; + const length = storageSizeLimit + 1; + for (let i = 0; i < length; i++) { + itemTooBigToFit += 'a'; + } + }); + + it('removes all items', () => { + setItemLater(hash, itemTooBigToFit); + expect(sessionStorage.getItem('b')).toEqual(null); + expect(sessionStorage.getItem('c')).toEqual(null); + }); + + it(`doesn't persist the item in sessionStorage`, () => { + setItemLater(hash, itemTooBigToFit); + expect(sessionStorage.getItem(hash)).toEqual(null); + }); + + it('returns false', () => { + const result = setItemLater(hash, itemTooBigToFit); + expect(result).toEqual(false); + }); + }); + }); + }); + }); + + describe('#getItem', () => { + describe('if the item exists in sessionStorage', () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + + function setItemLater(hash: string, item: string) { + // Move time forward, so this item will be "touched" most recently. + jest.advanceTimersByTime(1); + return hashedItemStore.setItem(hash, item); + } + + function getItemLater(hash: string) { + // Move time forward, so this item will be "touched" most recently. + jest.advanceTimersByTime(1); + return hashedItemStore.getItem(hash); + } + + beforeEach(() => { + // Control time. + jest.useFakeTimers(); + + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + hashedItemStore.setItem('1', 'a'); + }); + + afterEach(() => { + // Stop controlling time. + jest.useRealTimers(); + }); + + it('returns the item', () => { + const retrievedItem = hashedItemStore.getItem('1'); + expect(retrievedItem).toBe('a'); + }); + + it('prevents the item from being first to be removed when freeing up storage space', () => { + // Do this a little later so that this item is newer. + setItemLater('2', 'b'); + + // Wait a bit, then retrieve/touch the first item, making *it* newer, and 2 as the oldest. + getItemLater('1'); + + // Cap the storage at its current size. + const storageSizeLimit = sessionStorage.getStubbedSize(); + sessionStorage.setStubbedSizeLimit(storageSizeLimit); + + // Add a new item, causing the second item to be removed, but not the first. + setItemLater('3', 'c'); + expect(hashedItemStore.getItem('2')).toEqual(null); + expect(hashedItemStore.getItem('1')).toEqual('a'); + }); + }); + + describe(`if the item doesn't exist in sessionStorage`, () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + const hash = 'a'; + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + }); + + it('returns null', () => { + const retrievedItem = hashedItemStore.getItem(hash); + expect(retrievedItem).toBe(null); + }); + }); + }); + + describe('#removeItem', () => { + describe('if the item exists in sessionStorage', () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + hashedItemStore.setItem('1', 'a'); + hashedItemStore.setItem('2', 'b'); + }); + + it('removes and returns an item', () => { + const removedItem1 = hashedItemStore.removeItem('1'); + expect(removedItem1).toBe('a'); + expect(hashedItemStore.getItem('1')).toBeNull(); + expect(hashedItemStore.getItem('2')).not.toBeNull(); + expect((hashedItemStore as any).getIndexedItems()).toHaveLength(1); + + const removedItem2 = hashedItemStore.removeItem('2'); + expect(removedItem2).toBe('b'); + expect(hashedItemStore.getItem('1')).toBeNull(); + expect(hashedItemStore.getItem('2')).toBeNull(); + expect((hashedItemStore as any).getIndexedItems()).toHaveLength(0); + }); + }); + + describe(`if the item doesn't exist in sessionStorage`, () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + const hash = 'a'; + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + }); + + it('returns null', () => { + const removedItem = hashedItemStore.removeItem(hash); + expect(removedItem).toBe(null); + }); + }); + }); + + describe('#clear', () => { + describe('if the items exist in sessionStorage', () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + hashedItemStore.setItem('1', 'a'); + hashedItemStore.setItem('2', 'b'); + }); + + it('removes all items', () => { + hashedItemStore.clear(); + + expect(hashedItemStore.getItem('1')).toBeNull(); + expect(hashedItemStore.getItem('2')).toBeNull(); + expect((hashedItemStore as any).getIndexedItems()).toHaveLength(0); + }); + }); + + describe(`if items don't exist in sessionStorage`, () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + + beforeEach(() => { + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + }); + + it("doesn't throw", () => { + expect(() => hashedItemStore.clear()).not.toThrowError(); + }); + }); + }); + }); + + describe('behavior', () => { + let sessionStorage: Storage; + let hashedItemStore: HashedItemStore; + + function setItemLater(hash: string, item: string) { + // Move time forward, so this item will be "touched" most recently. + jest.advanceTimersByTime(1); + return hashedItemStore.setItem(hash, item); + } + + function getItemLater(hash: string) { + // Move time forward, so this item will be "touched" most recently. + jest.advanceTimersByTime(1); + return hashedItemStore.getItem(hash); + } + + beforeEach(() => { + // Control time. + jest.useFakeTimers(); + sessionStorage = new StubBrowserStorage(); + hashedItemStore = new HashedItemStore(sessionStorage); + }); + + afterEach(() => { + // Stop controlling time. + jest.useRealTimers(); + }); + + it('orders items to be removed based on when they were last retrieved', () => { + setItemLater('1', 'a'); + setItemLater('2', 'b'); + setItemLater('3', 'c'); + setItemLater('4', 'd'); + + // Cap the storage at its current size. + const storageSizeLimit = sessionStorage.getStubbedSize(); + sessionStorage.setStubbedSizeLimit(storageSizeLimit); + + // Expect items to be removed in order: 1, 3, 2, 4. + getItemLater('1'); + getItemLater('3'); + getItemLater('2'); + getItemLater('4'); + + setItemLater('5', 'e'); + expect(hashedItemStore.getItem('1')).toEqual(null); + expect(hashedItemStore.getItem('3')).toEqual('c'); + expect(hashedItemStore.getItem('2')).toEqual('b'); + expect(hashedItemStore.getItem('4')).toEqual('d'); + expect(hashedItemStore.getItem('5')).toEqual('e'); + + setItemLater('6', 'f'); + expect(hashedItemStore.getItem('3')).toEqual(null); + expect(hashedItemStore.getItem('2')).toEqual('b'); + expect(hashedItemStore.getItem('4')).toEqual('d'); + expect(hashedItemStore.getItem('5')).toEqual('e'); + expect(hashedItemStore.getItem('6')).toEqual('f'); + + setItemLater('7', 'g'); + expect(hashedItemStore.getItem('2')).toEqual(null); + expect(hashedItemStore.getItem('4')).toEqual('d'); + expect(hashedItemStore.getItem('5')).toEqual('e'); + expect(hashedItemStore.getItem('6')).toEqual('f'); + expect(hashedItemStore.getItem('7')).toEqual('g'); + + setItemLater('8', 'h'); + expect(hashedItemStore.getItem('4')).toEqual(null); + expect(hashedItemStore.getItem('5')).toEqual('e'); + expect(hashedItemStore.getItem('6')).toEqual('f'); + expect(hashedItemStore.getItem('7')).toEqual('g'); + expect(hashedItemStore.getItem('8')).toEqual('h'); + + setItemLater('9', 'i'); + expect(hashedItemStore.getItem('5')).toEqual(null); + expect(hashedItemStore.getItem('6')).toEqual('f'); + expect(hashedItemStore.getItem('7')).toEqual('g'); + expect(hashedItemStore.getItem('8')).toEqual('h'); + expect(hashedItemStore.getItem('9')).toEqual('i'); + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.ts new file mode 100644 index 00000000000000..485aa643c4f01c --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.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. + */ + +/** + * The HashedItemStore associates JSON objects with states in browser history and persists these + * objects in sessionStorage. We persist them so that when a tab is closed and re-opened, we can + * retain access to the state objects referenced by the browser history. + * + * Because there is a limit on how much data we can put into sessionStorage, the HashedItemStore + * will attempt to remove old items from storage once that limit is reached. + * + * ------------------------------------------------------------------------------------------------- + * + * Consideration 1: We can't (easily) mirror the browser history + * + * If we use letters to indicate a unique state object, and numbers to represent the same state + * occurring again (due to action by the user), a history could look like this: + * + * Old < - - - - - - - - > New + * A1 | B1 | C1 | A2 | D1 | E1 + * + * If the user navigates back to C1 and starts to create new states, persisted history states will + * become inaccessible: + * + * Old < - - - - - - - - - - -> New + * A1 | B1 | C1 | F1 | G1 | H1 | I1 (new history states) + * A2 | D1 | E1 (inaccessible persisted history states) + * + * Theoretically, we could build a mirror of the browser history. When the onpopstate event is + * dispatched, we could determine whether we have gone back or forward in history. Then, when + * a new state is persisted, we could delete all of the persisted items which are no longer + * accessible. (Note that this would require reference-counting so that A isn't removed while D and + * E are, since A would still have a remaining reference from A1). + * + * However, the History API doesn't allow us to read from the history beyond the current state. This + * means that if a session is restored, we can't rebuild this browser history mirror. + * + * Due to this imperfect implementation, HashedItemStore ignores the possibility of inaccessible + * history states. In the future, we could implement this history mirror and persist it in + * sessionStorage too. Then, when restoring a session, we can just retrieve it from sessionStorage. + * + * ------------------------------------------------------------------------------------------------- + * + * Consideration 2: We can't tell when we've hit the browser history limit + * + * Because some of our persisted history states may no longer be referenced by the browser history, + * and we have no way of knowing which ones, we have no way of knowing whether we've persisted a + * number of accessible states beyond the browser history length limit. + * + * More fundamentally, the browser history length limit is a browser implementation detail, so it + * can change from browser to browser, or over time. Respecting this limit would introduce a lot of + * (unnecessary?) complexity. + * + * For these reasons, HashedItemStore doesn't concern itself with this constraint. + */ + +import { pull, sortBy } from 'lodash'; +import { IStorage } from '../types'; + +interface IndexedItem { + hash: string; + touched?: number; // Date.now() +} + +export class HashedItemStore implements IStorage { + static readonly PERSISTED_INDEX_KEY = 'kbn.hashedItemsIndex.v1'; + private storage: Storage; + + /** + * HashedItemStore uses objects called indexed items to refer to items that have been persisted + * in storage. An indexed item is shaped {hash, touched}. The touched date is when the item + * was last referenced by the browser history. + */ + constructor(storage: Storage) { + this.storage = storage; + } + + setItem(hash: string, item: string): boolean { + const isItemPersisted = this.persistItem(hash, item); + + if (isItemPersisted) { + this.touchHash(hash); + } + + return isItemPersisted; + } + + getItem(hash: string): string | null { + const item = this.storage.getItem(hash); + + if (item !== null) { + this.touchHash(hash); + } + + return item; + } + + removeItem(hash: string): string | null { + const indexedItems = this.getIndexedItems(); + const itemToRemove = this.storage.getItem(hash); + const indexedItemToRemove = this.getIndexedItem(hash, indexedItems); + + if (indexedItemToRemove) { + pull(indexedItems, indexedItemToRemove); + this.setIndexedItems(indexedItems); + } + + if (itemToRemove) { + this.storage.removeItem(hash); + } + + return itemToRemove || null; + } + + clear() { + const indexedItems = this.getIndexedItems(); + indexedItems.forEach(({ hash }) => { + this.storage.removeItem(hash); + }); + this.setIndexedItems([]); + } + + // Store indexed items in descending order by touched (oldest first, newest last). We'll use + // this to remove older items when we run out of storage space. + private ensuredSorting = false; + private getIndexedItems(): IndexedItem[] { + // Restore a previously persisted index + const persistedItemIndex = this.storage.getItem(HashedItemStore.PERSISTED_INDEX_KEY); + let items = persistedItemIndex ? JSON.parse(persistedItemIndex) || [] : []; + + // ensure sorting once, as sorting all indexed items on each get is a performance hit + if (!this.ensuredSorting) { + items = sortBy(items, 'touched'); + this.setIndexedItems(items); + this.ensuredSorting = true; + } + + return items; + } + + private setIndexedItems(items: IndexedItem[]) { + this.storage.setItem(HashedItemStore.PERSISTED_INDEX_KEY, JSON.stringify(items)); + } + + private getIndexedItem(hash: string, indexedItems: IndexedItem[] = this.getIndexedItems()) { + return indexedItems.find(indexedItem => indexedItem.hash === hash); + } + + private persistItem(hash: string, item: string): boolean { + try { + this.storage.setItem(hash, item); + return true; + } catch (e) { + // If there was an error then we need to make some space for the item. + if (this.getIndexedItems().length === 0) { + // If there's nothing left to remove, then we've run out of space and we're trying to + // persist too large an item. + return false; + } + + // We need to try to make some space for the item by removing older items (i.e. items that + // haven't been accessed recently). + this.removeOldestItem(); + + // Try to persist again. + return this.persistItem(hash, item); + } + } + + private removeOldestItem() { + const indexedItems = this.getIndexedItems(); + const oldestIndexedItem = indexedItems.shift(); + if (oldestIndexedItem) { + // Remove oldest item from storage. + this.storage.removeItem(oldestIndexedItem.hash); + this.setIndexedItems(indexedItems); + } + } + + private touchHash(hash: string) { + const indexedItems = this.getIndexedItems(); + // Touching a hash indicates that it's been used recently, so it won't be the first in line + // when we remove items to free up storage space. + + // either get or create an indexedItem + const indexedItem = this.getIndexedItem(hash, indexedItems) || { hash }; + + // set/update the touched time to now so that it's the "newest" item in the index + indexedItem.touched = Date.now(); + + // ensure that the item is last in the index + pull(indexedItems, indexedItem); + indexedItems.push(indexedItem); + + // Regardless of whether this is a new or updated item, we need to persist the index. + this.setIndexedItems(indexedItems); + } +} diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/index.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/index.ts new file mode 100644 index 00000000000000..062266359c6c5e --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/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 { HashedItemStore } from './hashed_item_store'; +export { HashedItemStore }; +export const hashedItemStore = new HashedItemStore(window.sessionStorage); diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.ts new file mode 100644 index 00000000000000..e3360e0e3cf515 --- /dev/null +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.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 { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { HashedItemStore } from './hashed_item_store'; + +/** + * Useful for mocking state_storage from jest, + * + * import { mockSessionStorage } from '../state_storage/mock; + * + * And all tests in the test file will use HashedItemStoreSingleton + * with underlying mockSessionStorage we have access to + */ +export const mockStorage = new StubBrowserStorage(); +const mockHashedItemStore = new HashedItemStore(mockStorage); +jest.mock('./', () => { + return { + HashedItemStore: require('./hashed_item_store').HashedItemStore, + hashedItemStore: mockHashedItemStore, + }; +}); diff --git a/src/plugins/kibana_utils/public/storage/types.ts b/src/plugins/kibana_utils/public/storage/types.ts index 875bb44bcad17d..a25d4729fd3204 100644 --- a/src/plugins/kibana_utils/public/storage/types.ts +++ b/src/plugins/kibana_utils/public/storage/types.ts @@ -17,16 +17,16 @@ * under the License. */ -export interface IStorageWrapper { - get: (key: string) => any; - set: (key: string, value: any) => void; - remove: (key: string) => any; +export interface IStorageWrapper { + get: (key: string) => T | null; + set: (key: string, value: T) => S; + remove: (key: string) => T | null; clear: () => void; } -export interface IStorage { - getItem: (key: string) => any; - setItem: (key: string, value: any) => void; - removeItem: (key: string) => any; +export interface IStorage { + getItem: (key: string) => T | null; + setItem: (key: string, value: T) => S; + removeItem: (key: string) => T | null; clear: () => void; } diff --git a/src/plugins/kibana_utils/public/store/create_store.test.ts b/src/plugins/kibana_utils/public/store/create_store.test.ts deleted file mode 100644 index cfdeb76254003a..00000000000000 --- a/src/plugins/kibana_utils/public/store/create_store.test.ts +++ /dev/null @@ -1,177 +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 { createStore } from './create_store'; - -test('can create store', () => { - const store = createStore({}); - expect(store).toMatchObject({ - get: expect.any(Function), - set: expect.any(Function), - state$: expect.any(Object), - createMutators: expect.any(Function), - mutators: expect.any(Object), - redux: { - getState: expect.any(Function), - dispatch: expect.any(Function), - subscribe: expect.any(Function), - }, - }); -}); - -test('can set default state', () => { - const defaultState = { - foo: 'bar', - }; - const store = createStore(defaultState); - expect(store.get()).toEqual(defaultState); - expect(store.redux.getState()).toEqual(defaultState); -}); - -test('can set state', () => { - const defaultState = { - foo: 'bar', - }; - const newState = { - foo: 'baz', - }; - const store = createStore(defaultState); - - store.set(newState); - - expect(store.get()).toEqual(newState); - expect(store.redux.getState()).toEqual(newState); -}); - -test('does not shallow merge states', () => { - const defaultState = { - foo: 'bar', - }; - const newState = { - foo2: 'baz', - }; - const store = createStore(defaultState); - - store.set(newState); - - expect(store.get()).toEqual(newState); - expect(store.redux.getState()).toEqual(newState); -}); - -test('can subscribe and unsubscribe to state changes', () => { - const store = createStore({}); - const spy = jest.fn(); - const subscription = store.state$.subscribe(spy); - store.set({ a: 1 }); - store.set({ a: 2 }); - subscription.unsubscribe(); - store.set({ a: 3 }); - - expect(spy).toHaveBeenCalledTimes(2); - expect(spy.mock.calls[0][0]).toEqual({ a: 1 }); - expect(spy.mock.calls[1][0]).toEqual({ a: 2 }); -}); - -test('multiple subscribers can subscribe', () => { - const store = createStore({}); - const spy1 = jest.fn(); - const spy2 = jest.fn(); - const subscription1 = store.state$.subscribe(spy1); - const subscription2 = store.state$.subscribe(spy2); - store.set({ a: 1 }); - subscription1.unsubscribe(); - store.set({ a: 2 }); - subscription2.unsubscribe(); - store.set({ a: 3 }); - - expect(spy1).toHaveBeenCalledTimes(1); - expect(spy2).toHaveBeenCalledTimes(2); - expect(spy1.mock.calls[0][0]).toEqual({ a: 1 }); - expect(spy2.mock.calls[0][0]).toEqual({ a: 1 }); - expect(spy2.mock.calls[1][0]).toEqual({ a: 2 }); -}); - -test('creates impure mutators from pure mutators', () => { - const store = createStore({}); - const mutators = store.createMutators({ - setFoo: _ => bar => ({ foo: bar }), - }); - - expect(typeof mutators.setFoo).toBe('function'); -}); - -test('mutators can update state', () => { - const store = createStore({ - value: 0, - foo: 'bar', - }); - const mutators = store.createMutators({ - add: state => increment => ({ ...state, value: state.value + increment }), - setFoo: state => bar => ({ ...state, foo: bar }), - }); - - expect(store.get()).toEqual({ - value: 0, - foo: 'bar', - }); - - mutators.add(11); - mutators.setFoo('baz'); - - expect(store.get()).toEqual({ - value: 11, - foo: 'baz', - }); - - mutators.add(-20); - mutators.setFoo('bazooka'); - - expect(store.get()).toEqual({ - value: -9, - foo: 'bazooka', - }); -}); - -test('mutators methods are not bound', () => { - const store = createStore({ value: -3 }); - const { add } = store.createMutators({ - add: state => increment => ({ ...state, value: state.value + increment }), - }); - - expect(store.get()).toEqual({ value: -3 }); - add(4); - expect(store.get()).toEqual({ value: 1 }); -}); - -test('created mutators are saved in store object', () => { - const store = createStore< - any, - { - add: (increment: number) => void; - } - >({ value: -3 }); - - store.createMutators({ - add: state => increment => ({ ...state, value: state.value + increment }), - }); - - expect(typeof store.mutators.add).toBe('function'); - store.mutators.add(5); - expect(store.get()).toEqual({ value: 2 }); -}); diff --git a/src/plugins/kibana_utils/public/store/create_store.ts b/src/plugins/kibana_utils/public/store/create_store.ts deleted file mode 100644 index 315523360f92d5..00000000000000 --- a/src/plugins/kibana_utils/public/store/create_store.ts +++ /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. - */ - -import { createStore as createReduxStore, Reducer } from 'redux'; -import { Subject, Observable } from 'rxjs'; -import { AppStore, Mutators, PureMutators } from './types'; - -const SET = '__SET__'; - -export const createStore = < - State extends {}, - StateMutators extends Mutators> = {} ->( - defaultState: State -): AppStore => { - const pureMutators: PureMutators = {}; - const mutators: StateMutators = {} as StateMutators; - const reducer: Reducer = (state, action) => { - const pureMutator = pureMutators[action.type]; - if (pureMutator) { - return pureMutator(state)(...action.args); - } - - switch (action.type) { - case SET: - return action.state; - default: - return state; - } - }; - const redux = createReduxStore(reducer, defaultState as any); - - const get = redux.getState; - - const set = (state: State) => - redux.dispatch({ - type: SET, - state, - }); - - const state$ = new Subject(); - redux.subscribe(() => { - state$.next(get()); - }); - - const createMutators: AppStore['createMutators'] = newPureMutators => { - const result: Mutators = {}; - for (const type of Object.keys(newPureMutators)) { - result[type] = (...args) => { - redux.dispatch({ - type, - args, - }); - }; - } - Object.assign(pureMutators, newPureMutators); - Object.assign(mutators, result); - return result; - }; - - return { - get, - set, - redux, - state$: (state$ as unknown) as Observable, - createMutators, - mutators, - }; -}; diff --git a/src/plugins/kibana_utils/public/store/index.ts b/src/plugins/kibana_utils/public/store/index.ts deleted file mode 100644 index 468e8ab8c5adee..00000000000000 --- a/src/plugins/kibana_utils/public/store/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 * from './create_store'; -export * from './react'; diff --git a/src/plugins/kibana_utils/public/store/observable_selector.ts b/src/plugins/kibana_utils/public/store/observable_selector.ts deleted file mode 100644 index 6ba6f42296a6c8..00000000000000 --- a/src/plugins/kibana_utils/public/store/observable_selector.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 { Observable, BehaviorSubject } from 'rxjs'; - -export type Selector = (state: State) => Result; -export type Comparator = (previous: Result, current: Result) => boolean; -export type Unsubscribe = () => void; - -const defaultComparator: Comparator = (previous, current) => previous === current; - -export const observableSelector = ( - state: State, - state$: Observable, - selector: Selector, - comparator: Comparator = defaultComparator -): [Observable, Unsubscribe] => { - let previousResult: Result = selector(state); - const result$ = new BehaviorSubject(previousResult); - - const subscription = state$.subscribe(value => { - const result = selector(value); - const isEqual: boolean = comparator(previousResult, result); - if (!isEqual) { - result$.next(result); - } - previousResult = result; - }); - - return [(result$ as unknown) as Observable, subscription.unsubscribe]; -}; diff --git a/src/plugins/kibana_utils/public/store/react.test.tsx b/src/plugins/kibana_utils/public/store/react.test.tsx deleted file mode 100644 index e629e9d0e12573..00000000000000 --- a/src/plugins/kibana_utils/public/store/react.test.tsx +++ /dev/null @@ -1,389 +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 * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { act, Simulate } from 'react-dom/test-utils'; -import { createStore } from './create_store'; -import { createContext } from './react'; - -let container: HTMLDivElement | null; - -beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); -}); - -afterEach(() => { - document.body.removeChild(container!); - container = null; -}); - -test('can create React context', () => { - const store = createStore({ foo: 'bar' }); - const context = createContext(store); - - expect(context).toMatchObject({ - Provider: expect.any(Function), - Consumer: expect.any(Function), - connect: expect.any(Function), - context: { - Provider: expect.any(Object), - Consumer: expect.any(Object), - }, - }); -}); - -test(' passes state to ', () => { - const store = createStore({ hello: 'world' }); - const { Provider, Consumer } = createContext(store); - - ReactDOM.render( - - {({ hello }) => hello} - , - container - ); - - expect(container!.innerHTML).toBe('world'); -}); - -interface State1 { - hello: string; -} - -interface Props1 { - message: string; - stop: '.' | '!' | '?'; -} - -test(' passes state to connect()()', () => { - const store = createStore({ hello: 'Bob' }); - const { Provider, connect } = createContext(store); - - const Demo: React.FC = ({ message, stop }) => ( - <> - {message} - {stop} - - ); - const mergeProps = ({ hello }: State1) => ({ message: hello }); - const DemoConnected = connect(mergeProps)(Demo); - - ReactDOM.render( - - - , - container - ); - - expect(container!.innerHTML).toBe('Bob?'); -}); - -test('context receives Redux store', () => { - const store = createStore({ foo: 'bar' }); - const { Provider, context } = createContext(store); - - ReactDOM.render( - /* eslint-disable no-shadow */ - - {({ store }) => store.getState().foo} - , - /* eslint-enable no-shadow */ - container - ); - - expect(container!.innerHTML).toBe('bar'); -}); - -xtest('can use multiple stores in one React app', () => {}); - -describe('hooks', () => { - describe('useStore', () => { - test('can select store using useStore hook', () => { - const store = createStore({ foo: 'bar' }); - const { Provider, useStore } = createContext(store); - const Demo: React.FC<{}> = () => { - // eslint-disable-next-line no-shadow - const store = useStore(); - return <>{store.get().foo}; - }; - - ReactDOM.render( - - - , - container - ); - - expect(container!.innerHTML).toBe('bar'); - }); - }); - - describe('useState', () => { - test('can select state using useState hook', () => { - const store = createStore({ foo: 'qux' }); - const { Provider, useState } = createContext(store); - const Demo: React.FC<{}> = () => { - const { foo } = useState(); - return <>{foo}; - }; - - ReactDOM.render( - - - , - container - ); - - expect(container!.innerHTML).toBe('qux'); - }); - - test('re-renders when state changes', () => { - const store = createStore({ foo: 'bar' }); - const { setFoo } = store.createMutators({ - setFoo: state => foo => ({ ...state, foo }), - }); - const { Provider, useState } = createContext(store); - const Demo: React.FC<{}> = () => { - const { foo } = useState(); - return <>{foo}; - }; - - ReactDOM.render( - - - , - container - ); - - expect(container!.innerHTML).toBe('bar'); - act(() => { - setFoo('baz'); - }); - expect(container!.innerHTML).toBe('baz'); - }); - }); - - describe('useMutations', () => { - test('useMutations hook returns mutations that can update state', () => { - const store = createStore< - { - cnt: number; - }, - { - increment: (value: number) => void; - } - >({ - cnt: 0, - }); - store.createMutators({ - increment: state => value => ({ ...state, cnt: state.cnt + value }), - }); - - const { Provider, useState, useMutators } = createContext(store); - const Demo: React.FC<{}> = () => { - const { cnt } = useState(); - const { increment } = useMutators(); - return ( - <> - {cnt} - - - ); - }; - - ReactDOM.render( - - - , - container - ); - - expect(container!.querySelector('strong')!.innerHTML).toBe('0'); - act(() => { - Simulate.click(container!.querySelector('button')!, {}); - }); - expect(container!.querySelector('strong')!.innerHTML).toBe('10'); - act(() => { - Simulate.click(container!.querySelector('button')!, {}); - }); - expect(container!.querySelector('strong')!.innerHTML).toBe('20'); - }); - }); - - describe('useSelector', () => { - test('can select deeply nested value', () => { - const store = createStore({ - foo: { - bar: { - baz: 'qux', - }, - }, - }); - const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; - const { Provider, useSelector } = createContext(store); - const Demo: React.FC<{}> = () => { - const value = useSelector(selector); - return <>{value}; - }; - - ReactDOM.render( - - - , - container - ); - - expect(container!.innerHTML).toBe('qux'); - }); - - test('re-renders when state changes', () => { - const store = createStore({ - foo: { - bar: { - baz: 'qux', - }, - }, - }); - const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; - const { Provider, useSelector } = createContext(store); - const Demo: React.FC<{}> = () => { - const value = useSelector(selector); - return <>{value}; - }; - - ReactDOM.render( - - - , - container - ); - - expect(container!.innerHTML).toBe('qux'); - act(() => { - store.set({ - foo: { - bar: { - baz: 'quux', - }, - }, - }); - }); - expect(container!.innerHTML).toBe('quux'); - }); - - test("re-renders only when selector's result changes", async () => { - const store = createStore({ a: 'b', foo: 'bar' }); - const selector = (state: { foo: string }) => state.foo; - const { Provider, useSelector } = createContext(store); - - let cnt = 0; - const Demo: React.FC<{}> = () => { - cnt++; - const value = useSelector(selector); - return <>{value}; - }; - ReactDOM.render( - - - , - container - ); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(1); - - act(() => { - store.set({ a: 'c', foo: 'bar' }); - }); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(1); - - act(() => { - store.set({ a: 'd', foo: 'bar 2' }); - }); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(2); - }); - - test('re-renders on same shape object', async () => { - const store = createStore({ foo: { bar: 'baz' } }); - const selector = (state: { foo: any }) => state.foo; - const { Provider, useSelector } = createContext(store); - - let cnt = 0; - const Demo: React.FC<{}> = () => { - cnt++; - const value = useSelector(selector); - return <>{JSON.stringify(value)}; - }; - ReactDOM.render( - - - , - container - ); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(1); - - act(() => { - store.set({ foo: { bar: 'baz' } }); - }); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(2); - }); - - test('can set custom comparator function to prevent re-renders on deep equality', async () => { - const store = createStore({ foo: { bar: 'baz' } }); - const selector = (state: { foo: any }) => state.foo; - const comparator = (prev: any, curr: any) => JSON.stringify(prev) === JSON.stringify(curr); - const { Provider, useSelector } = createContext(store); - - let cnt = 0; - const Demo: React.FC<{}> = () => { - cnt++; - const value = useSelector(selector, comparator); - return <>{JSON.stringify(value)}; - }; - ReactDOM.render( - - - , - container - ); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(1); - - act(() => { - store.set({ foo: { bar: 'baz' } }); - }); - - await new Promise(r => setTimeout(r, 1)); - expect(cnt).toBe(1); - }); - - xtest('unsubscribes when React un-mounts', () => {}); - }); -}); diff --git a/src/plugins/kibana_utils/public/store/react.ts b/src/plugins/kibana_utils/public/store/react.ts deleted file mode 100644 index 00861b2b0b8fef..00000000000000 --- a/src/plugins/kibana_utils/public/store/react.ts +++ /dev/null @@ -1,126 +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 * as React from 'react'; -import { Provider as ReactReduxProvider, connect as reactReduxConnect } from 'react-redux'; -import { Store } from 'redux'; -import { AppStore, Mutators, PureMutators } from './types'; -import { observableSelector, Selector, Comparator } from './observable_selector'; -// TODO: Below import is temporary, use `react-use` lib instead. -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { useObservable } from '../../../kibana_react/public/util/use_observable'; - -const { useMemo, useLayoutEffect, useContext, createElement, Fragment } = React; - -/** - * @note - * Types in `react-redux` seem to be quite off compared to reality - * that's why a lot of `any`s below. - */ - -export interface ConsumerProps { - children: (state: State) => React.ReactChild; -} - -export type MapStateToProps = (state: State) => StateProps; - -// TODO: `Omit` is generally part of TypeScript, but it currently does not exist in our build. -type Omit = Pick>; -export type Connect = ( - mapStateToProp: MapStateToProps> -) => (component: React.ComponentType) => React.FC>; - -interface ReduxContextValue { - store: Store; -} - -const mapDispatchToProps = () => ({}); -const mergeProps: any = (stateProps: any, dispatchProps: any, ownProps: any) => ({ - ...ownProps, - ...stateProps, - ...dispatchProps, -}); - -export const createContext = < - State extends {}, - StateMutators extends Mutators> = {} ->( - store: AppStore -) => { - const { redux } = store; - (redux as any).__appStore = store; - const context = React.createContext({ store: redux }); - - const useStore = (): AppStore => { - // eslint-disable-next-line no-shadow - const { store } = useContext(context); - return (store as any).__appStore; - }; - - const useState = (): State => { - const { state$, get } = useStore(); - const state = useObservable(state$, get()); - return state; - }; - - const useMutators = (): StateMutators => useStore().mutators; - - const useSelector = ( - selector: Selector, - 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; - }; - - const Provider: React.FC<{}> = ({ children }) => - createElement(ReactReduxProvider, { - store: redux, - context, - children, - } as any); - - const Consumer: React.FC> = ({ children }) => { - const state = useState(); - return createElement(Fragment, { children: children(state) }); - }; - - const options: any = { context }; - const connect: Connect = mapStateToProps => - reactReduxConnect(mapStateToProps, mapDispatchToProps, mergeProps, options) as any; - - return { - Provider, - Consumer, - connect, - context, - useStore, - useState, - useMutators, - useSelector, - }; -}; diff --git a/src/plugins/kibana_utils/public/store/types.ts b/src/plugins/kibana_utils/public/store/types.ts deleted file mode 100644 index 952ee07f18baf6..00000000000000 --- a/src/plugins/kibana_utils/public/store/types.ts +++ /dev/null @@ -1,42 +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 { Observable } from 'rxjs'; -import { Store as ReduxStore } from 'redux'; - -export interface AppStore< - State extends {}, - StateMutators extends Mutators> = {} -> { - redux: ReduxStore; - get: () => State; - set: (state: State) => void; - state$: Observable; - createMutators: >(pureMutators: M) => Mutators; - mutators: StateMutators; -} - -export type PureMutator = (state: State) => (...args: any[]) => State; -export type Mutator> = (...args: Parameters>) => void; - -export interface PureMutators { - [name: string]: PureMutator; -} - -export type Mutators> = { [K in keyof M]: Mutator }; diff --git a/src/plugins/share/kibana.json b/src/plugins/share/kibana.json index bbe393a76c5dae..dce2ac9281aba7 100644 --- a/src/plugins/share/kibana.json +++ b/src/plugins/share/kibana.json @@ -1,6 +1,6 @@ { "id": "share", "version": "kibana", - "server": false, + "server": true, "ui": true } diff --git a/src/plugins/share/public/components/share_context_menu.test.tsx b/src/plugins/share/public/components/share_context_menu.test.tsx index 7fb0449ead5020..1f2242ae4c5158 100644 --- a/src/plugins/share/public/components/share_context_menu.test.tsx +++ b/src/plugins/share/public/components/share_context_menu.test.tsx @@ -34,7 +34,7 @@ const defaultProps = { isDirty: false, onClose: () => {}, basePath: '', - post: () => Promise.resolve(), + post: () => Promise.resolve({} as any), objectType: 'dashboard', }; diff --git a/src/plugins/share/public/components/url_panel_content.test.tsx b/src/plugins/share/public/components/url_panel_content.test.tsx index 9da1a23641ab86..9db8d1ccf2efa2 100644 --- a/src/plugins/share/public/components/url_panel_content.test.tsx +++ b/src/plugins/share/public/components/url_panel_content.test.tsx @@ -28,7 +28,7 @@ const defaultProps = { allowShortUrl: true, objectType: 'dashboard', basePath: '', - post: () => Promise.resolve(), + post: () => Promise.resolve({} as any), }; test('render', () => { diff --git a/src/plugins/share/server/index.ts b/src/plugins/share/server/index.ts new file mode 100644 index 00000000000000..9e574314f80000 --- /dev/null +++ b/src/plugins/share/server/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../core/server'; +import { SharePlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SharePlugin(initializerContext); +} diff --git a/src/plugins/share/server/plugin.ts b/src/plugins/share/server/plugin.ts new file mode 100644 index 00000000000000..bcb681a50652a8 --- /dev/null +++ b/src/plugins/share/server/plugin.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 { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; +import { createRoutes } from './routes/create_routes'; + +export class SharePlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public async setup(core: CoreSetup) { + createRoutes(core, this.initializerContext.logger.get()); + } + + public start() { + this.initializerContext.logger.get().debug('Starting plugin'); + } + + public stop() { + this.initializerContext.logger.get().debug('Stopping plugin'); + } +} diff --git a/src/plugins/share/server/routes/create_routes.ts b/src/plugins/share/server/routes/create_routes.ts new file mode 100644 index 00000000000000..bd4b6fdb08791d --- /dev/null +++ b/src/plugins/share/server/routes/create_routes.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 { CoreSetup, Logger } from 'kibana/server'; + +import { shortUrlLookupProvider } from './lib/short_url_lookup'; +import { createGotoRoute } from './goto'; +import { createShortenUrlRoute } from './shorten_url'; + +export function createRoutes({ http }: CoreSetup, logger: Logger) { + const shortUrlLookup = shortUrlLookupProvider({ logger }); + const router = http.createRouter(); + + createGotoRoute({ router, shortUrlLookup, http }); + createShortenUrlRoute({ router, shortUrlLookup }); +} diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts new file mode 100644 index 00000000000000..7343dc1bd34a26 --- /dev/null +++ b/src/plugins/share/server/routes/goto.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 { CoreSetup, IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; + +export const createGotoRoute = ({ + router, + shortUrlLookup, + http, +}: { + router: IRouter; + shortUrlLookup: ShortUrlLookupService; + http: CoreSetup['http']; +}) => { + router.get( + { + path: '/goto/{urlId}', + validate: { + params: schema.object({ urlId: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + const url = await shortUrlLookup.getUrl(request.params.urlId, { + savedObjects: context.core.savedObjects.client, + }); + shortUrlAssertValid(url); + + const uiSettings = context.core.uiSettings.client; + const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage'); + if (!stateStoreInSessionStorage) { + return response.redirected({ + headers: { + location: http.basePath.prepend(url), + }, + }); + } + return response.redirected({ + headers: { + location: http.basePath.prepend('/goto_LP/' + request.params.urlId), + }, + }); + }) + ); +}; diff --git a/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts b/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts new file mode 100644 index 00000000000000..f83073e6aefe90 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_assert_valid.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 { shortUrlAssertValid } from './short_url_assert_valid'; + +describe('shortUrlAssertValid()', () => { + const invalid = [ + ['protocol', 'http://localhost:5601/app/kibana'], + ['protocol', 'https://localhost:5601/app/kibana'], + ['protocol', 'mailto:foo@bar.net'], + ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url + ['hostname', 'localhost/app/kibana'], + ['hostname and port', 'local.host:5601/app/kibana'], + ['hostname and auth', 'user:pass@localhost.net/app/kibana'], + ['path traversal', '/app/../../not-kibana'], + ['deep path', '/app/kibana/foo'], + ['deep path', '/app/kibana/foo/bar'], + ['base path', '/base/app/kibana'], + ]; + + invalid.forEach(([desc, url]) => { + it(`fails when url has ${desc}`, () => { + try { + shortUrlAssertValid(url); + throw new Error(`expected assertion to throw`); + } catch (err) { + if (!err || !err.isBoom) { + throw err; + } + } + }); + }); + + const valid = [ + '/app/kibana', + '/app/monitoring#angular/route', + '/app/text#document-id', + '/app/some?with=query', + '/app/some?with=query#and-a-hash', + ]; + + valid.forEach(url => { + it(`allows ${url}`, () => { + shortUrlAssertValid(url); + }); + }); +}); diff --git a/src/plugins/share/server/routes/lib/short_url_assert_valid.ts b/src/plugins/share/server/routes/lib/short_url_assert_valid.ts new file mode 100644 index 00000000000000..2f120bbc03cd73 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_assert_valid.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 { parse } from 'url'; +import { trim } from 'lodash'; +import Boom from 'boom'; + +export function shortUrlAssertValid(url: string) { + const { protocol, hostname, pathname } = parse(url); + + if (protocol) { + throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`); + } + + if (hostname) { + throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`); + } + + const pathnameParts = trim(pathname, '/').split('/'); + if (pathnameParts.length !== 2) { + throw Boom.notAcceptable( + `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"` + ); + } +} diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts new file mode 100644 index 00000000000000..87e2b7b726e599 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { shortUrlLookupProvider, ShortUrlLookupService } from './short_url_lookup'; +import { SavedObjectsClientContract, Logger } from 'kibana/server'; +import { SavedObjectsClient } from '../../../../../core/server'; + +describe('shortUrlLookupProvider', () => { + const ID = 'bf00ad16941fc51420f91a93428b27a0'; + const TYPE = 'url'; + const URL = 'http://elastic.co'; + + let savedObjects: jest.Mocked; + let deps: { savedObjects: SavedObjectsClientContract }; + let shortUrl: ShortUrlLookupService; + + beforeEach(() => { + savedObjects = ({ + get: jest.fn(), + create: jest.fn(() => Promise.resolve({ id: ID })), + update: jest.fn(), + errors: SavedObjectsClient.errors, + } as unknown) as jest.Mocked; + + deps = { savedObjects }; + shortUrl = shortUrlLookupProvider({ logger: ({ warn: () => {} } as unknown) as Logger }); + }); + + describe('generateUrlId', () => { + it('returns the document id', async () => { + const id = await shortUrl.generateUrlId(URL, deps); + expect(id).toEqual(ID); + }); + + it('provides correct arguments to savedObjectsClient', async () => { + await shortUrl.generateUrlId(URL, { savedObjects }); + + expect(savedObjects.create).toHaveBeenCalledTimes(1); + const [type, attributes, options] = savedObjects.create.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(Object.keys(attributes).sort()).toEqual([ + 'accessCount', + 'accessDate', + 'createDate', + 'url', + ]); + expect(attributes.url).toEqual(URL); + expect(options!.id).toEqual(ID); + }); + + it('passes persists attributes', async () => { + await shortUrl.generateUrlId(URL, deps); + + expect(savedObjects.create).toHaveBeenCalledTimes(1); + const [type, attributes] = savedObjects.create.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(Object.keys(attributes).sort()).toEqual([ + 'accessCount', + 'accessDate', + 'createDate', + 'url', + ]); + expect(attributes.url).toEqual(URL); + }); + + it('gracefully handles version conflict', async () => { + const error = savedObjects.errors.decorateConflictError(new Error()); + savedObjects.create.mockImplementation(() => { + throw error; + }); + const id = await shortUrl.generateUrlId(URL, deps); + expect(id).toEqual(ID); + }); + }); + + describe('getUrl', () => { + beforeEach(() => { + const attributes = { accessCount: 2, url: URL }; + savedObjects.get.mockResolvedValue({ id: ID, attributes, type: 'url', references: [] }); + }); + + it('provides the ID to savedObjectsClient', async () => { + await shortUrl.getUrl(ID, { savedObjects }); + + expect(savedObjects.get).toHaveBeenCalledTimes(1); + expect(savedObjects.get).toHaveBeenCalledWith(TYPE, ID); + }); + + it('returns the url', async () => { + const response = await shortUrl.getUrl(ID, deps); + expect(response).toEqual(URL); + }); + + it('increments accessCount', async () => { + await shortUrl.getUrl(ID, { savedObjects }); + + expect(savedObjects.update).toHaveBeenCalledTimes(1); + + const [type, id, attributes] = savedObjects.update.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(id).toEqual(ID); + expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); + expect(attributes.accessCount).toEqual(3); + }); + }); +}); diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.ts b/src/plugins/share/server/routes/lib/short_url_lookup.ts new file mode 100644 index 00000000000000..0d8a9c86621de3 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_lookup.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import crypto from 'crypto'; +import { get } from 'lodash'; + +import { Logger, SavedObject, SavedObjectsClientContract } from 'kibana/server'; + +export interface ShortUrlLookupService { + generateUrlId(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; + getUrl(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; +} + +export function shortUrlLookupProvider({ logger }: { logger: Logger }): ShortUrlLookupService { + async function updateMetadata( + doc: SavedObject, + { savedObjects }: { savedObjects: SavedObjectsClientContract } + ) { + try { + await savedObjects.update('url', doc.id, { + accessDate: new Date().valueOf(), + accessCount: get(doc, 'attributes.accessCount', 0) + 1, + }); + } catch (error) { + logger.warn('Warning: Error updating url metadata'); + logger.warn(error); + // swallow errors. It isn't critical if there is no update. + } + } + + return { + async generateUrlId(url, { savedObjects }) { + const id = crypto + .createHash('md5') + .update(url) + .digest('hex'); + const { isConflictError } = savedObjects.errors; + + try { + const doc = await savedObjects.create( + 'url', + { + url, + accessCount: 0, + createDate: new Date().valueOf(), + accessDate: new Date().valueOf(), + }, + { id } + ); + + return doc.id; + } catch (error) { + if (isConflictError(error)) { + return id; + } + + throw error; + } + }, + + async getUrl(id, { savedObjects }) { + const doc = await savedObjects.get('url', id); + updateMetadata(doc, { savedObjects }); + + return doc.attributes.url; + }, + }; +} diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts new file mode 100644 index 00000000000000..116b90c6971c5c --- /dev/null +++ b/src/plugins/share/server/routes/shorten_url.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 { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; + +export const createShortenUrlRoute = ({ + shortUrlLookup, + router, +}: { + shortUrlLookup: ShortUrlLookupService; + router: IRouter; +}) => { + router.post( + { + path: '/api/shorten_url', + validate: { + body: schema.object({ url: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + shortUrlAssertValid(request.body.url); + const urlId = await shortUrlLookup.generateUrlId(request.body.url, { + savedObjects: context.core.savedObjects.client, + }); + return response.ok({ body: { urlId } }); + }) + ); +}; diff --git a/src/test_utils/public/stub_browser_storage.test.ts b/src/test_utils/public/stub_browser_storage.test.ts index 7a02221c520517..26070cde25232f 100644 --- a/src/test_utils/public/stub_browser_storage.test.ts +++ b/src/test_utils/public/stub_browser_storage.test.ts @@ -39,6 +39,18 @@ describe('StubBrowserStorage', () => { }); }); + describe('#clear()', () => { + it('clears items', () => { + const store = new StubBrowserStorage(); + store.setItem('1', '1'); + store.setItem('2', '2'); + store.clear(); + expect(store.getItem('1')).toBe(null); + expect(store.getItem('2')).toBe(null); + expect(store.length).toBe(0); + }); + }); + describe('#length', () => { it('reports the number of items stored', () => { const store = new StubBrowserStorage(); diff --git a/src/test_utils/public/stub_browser_storage.ts b/src/test_utils/public/stub_browser_storage.ts index aa6820d9438189..b5ee9a24e4c2a0 100644 --- a/src/test_utils/public/stub_browser_storage.ts +++ b/src/test_utils/public/stub_browser_storage.ts @@ -17,9 +17,9 @@ * under the License. */ -export class StubBrowserStorage { - private readonly keys: string[] = []; - private readonly values: string[] = []; +export class StubBrowserStorage implements Storage { + private keys: string[] = []; + private values: string[] = []; private size = 0; private sizeLimit = 5000000; // 5mb, minimum browser storage size; @@ -73,6 +73,12 @@ export class StubBrowserStorage { this.values.splice(i, 1); } + public clear() { + this.size = 0; + this.keys = []; + this.values = []; + } + // ----------------------------------------------------------------------------------------------- // Test-specific methods. // ----------------------------------------------------------------------------------------------- diff --git a/src/test_utils/public/stub_field_formats.ts b/src/test_utils/public/stub_field_formats.ts index 39c6fb2f6d10ea..da1a31f1cc7a53 100644 --- a/src/test_utils/public/stub_field_formats.ts +++ b/src/test_utils/public/stub_field_formats.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { UiSettingsClientContract } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; import { FieldFormatRegisty, @@ -37,7 +37,7 @@ import { UrlFormat, } from '../../plugins/data/public/'; -export const getFieldFormatsRegistry = (uiSettings: UiSettingsClientContract) => { +export const getFieldFormatsRegistry = (uiSettings: IUiSettingsClient) => { const fieldFormats = new FieldFormatRegisty(); fieldFormats.register([ diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index b41ebe3e618615..f4659ffa120d40 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -21,20 +21,26 @@ import sinon from 'sinon'; // TODO: We should not be importing from the data plugin directly here; this is only necessary // because it is one of the few places that we need to access the IndexPattern class itself, rather // than just the type. Doing this as a temporary measure; it will be left behind when migrating to NP. -import { IndexPattern } from '../../legacy/core_plugins/data/public/index_patterns/index_patterns'; + import { FieldList, - getRoutes, - formatHitProvider, - flattenHitWrapper, -} from 'ui/index_patterns'; -import { FIELD_FORMAT_IDS, + IndexPattern, + indexPatterns, } from '../../plugins/data/public'; +import { setFieldFormats } from '../../plugins/data/public/services'; + +setFieldFormats({ + getDefaultInstance: () => ({ + getConverterFor: () => value => value, + convert: value => JSON.stringify(value) + }), +}); + import { getFieldFormatsRegistry } from './stub_field_formats'; -export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { +export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { const registeredFieldFormats = getFieldFormatsRegistry(uiSettings); this.id = pattern; @@ -49,11 +55,11 @@ export default function StubIndexPattern(pattern, getConfig, timeField, fields, this.getSourceFiltering = sinon.stub(); this.metaFields = ['_id', '_type', '_source']; this.fieldFormatMap = {}; - this.routes = getRoutes(); + this.routes = indexPatterns.getRoutes(); this.getComputedFields = IndexPattern.prototype.getComputedFields.bind(this); - this.flattenHit = flattenHitWrapper(this, this.metaFields); - this.formatHit = formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); + this.flattenHit = indexPatterns.flattenHitWrapper(this, this.metaFields); + this.formatHit = indexPatterns.formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); this.fieldsFetcher = { apiClient: { baseUrl: '' } }; this.formatField = this.formatHit.formatField; diff --git a/style_guides/accessibility_guide.md b/style_guides/accessibility_guide.md index 28fb69ec38185b..4827d939915105 100644 --- a/style_guides/accessibility_guide.md +++ b/style_guides/accessibility_guide.md @@ -1,59 +1,25 @@ # Accessibility (A11Y) Guide -This document provides some technical guidelines how to prevent several common -accessibility issues. +[EUI's accessibility guidelines](https://elastic.github.io/eui/#/guidelines/accessibility) should be your first stop for all things. -## Naming elements +## Automated accessibility testing -### `aria-label` and `aria-labelledby` +To run the tests locally: -Every element on a page will have a name, that is read out to an assistive technology -like a screen reader. This will for most elements be the content of the element. -For form elements it will be the content of the associated `