diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage new file mode 100644 index 0000000000000..d9ec1861c9979 --- /dev/null +++ b/.ci/Jenkinsfile_coverage @@ -0,0 +1,112 @@ +#!/bin/groovy + +library 'kibana-pipeline-library' +kibanaLibrary.load() // load from the Jenkins instance + +stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a little bit + timeout(time: 180, unit: 'MINUTES') { + timestamps { + ansiColor('xterm') { + catchError { + withEnv([ + 'CODE_COVERAGE=1', // Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc. + ]) { + parallel([ + 'kibana-intake-agent': { + withEnv([ + 'NODE_ENV=test' // Needed for jest tests only + ]) { + kibanaPipeline.legacyJobRunner('kibana-intake')() + } + }, + 'x-pack-intake-agent': { + withEnv([ + 'NODE_ENV=test' // Needed for jest tests only + ]) { + 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), + ]), + 'kibana-xpack-agent-1': kibanaPipeline.withWorkers('kibana-xpack-tests-1', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), + 'xpack-ciGroup2': kibanaPipeline.getXpackCiGroupWorker(2), + ]), + 'kibana-xpack-agent-2': kibanaPipeline.withWorkers('kibana-xpack-tests-2', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup3': kibanaPipeline.getXpackCiGroupWorker(3), + 'xpack-ciGroup4': kibanaPipeline.getXpackCiGroupWorker(4), + ]), + + 'kibana-xpack-agent-3': kibanaPipeline.withWorkers('kibana-xpack-tests-3', { kibanaPipeline.buildXpack() }, [ + '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), + ]), + ]) + kibanaPipeline.jobRunner('tests-l', false) { + kibanaPipeline.downloadCoverageArtifacts() + kibanaPipeline.bash( + ''' + # bootstrap from x-pack folder + source src/dev/ci_setup/setup_env.sh + cd x-pack + yarn kbn bootstrap --prefer-offline + cd .. + # extract archives + mkdir -p /tmp/extracted_coverage + echo extracting intakes + tar -xzf /tmp/downloaded_coverage/coverage/kibana-intake/kibana-coverage.tar.gz -C /tmp/extracted_coverage + tar -xzf /tmp/downloaded_coverage/coverage/x-pack-intake/kibana-coverage.tar.gz -C /tmp/extracted_coverage + echo extracting kibana-oss-tests + tar -xzf /tmp/downloaded_coverage/coverage/kibana-oss-tests/kibana-coverage.tar.gz -C /tmp/extracted_coverage + echo extracting kibana-xpack-tests + for i in {1..3}; do + tar -xzf /tmp/downloaded_coverage/coverage/kibana-xpack-tests-${i}/kibana-coverage.tar.gz -C /tmp/extracted_coverage + done + # replace path in json files to have valid html report + pwd=$(pwd) + du -sh /tmp/extracted_coverage/target/kibana-coverage/ + echo replacing path in json files + for i in {1..9}; do + sed -i "s|/dev/shm/workspace/kibana|$pwd|g" /tmp/extracted_coverage/target/kibana-coverage/functional/${i}*.json & + done + wait + # merge oss & x-pack reports + echo merging coverage reports + yarn nyc report --temp-dir /tmp/extracted_coverage/target/kibana-coverage/jest --report-dir target/kibana-coverage/jest-combined --reporter=html --reporter=json-summary + yarn nyc report --temp-dir /tmp/extracted_coverage/target/kibana-coverage/functional --report-dir target/kibana-coverage/functional-combined --reporter=html --reporter=json-summary + echo copy mocha reports + mkdir -p target/kibana-coverage/mocha-combined + cp -r /tmp/extracted_coverage/target/kibana-coverage/mocha target/kibana-coverage/mocha-combined + ''', + "run `yarn kbn bootstrap && merge coverage`" + ) + sh 'tar -czf kibana-jest-coverage.tar.gz target/kibana-coverage/jest-combined/*' + kibanaPipeline.uploadCoverageArtifacts("coverage/jest-combined", 'kibana-jest-coverage.tar.gz') + sh 'tar -czf kibana-functional-coverage.tar.gz target/kibana-coverage/functional-combined/*' + kibanaPipeline.uploadCoverageArtifacts("coverage/functional-combined", 'kibana-functional-coverage.tar.gz') + sh 'tar -czf kibana-mocha-coverage.tar.gz target/kibana-coverage/mocha-combined/*' + kibanaPipeline.uploadCoverageArtifacts("coverage/mocha-combined", 'kibana-mocha-coverage.tar.gz') + } + } + } + kibanaPipeline.sendMail() + } + } + } +} diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index 669395564db44..f702405aad69e 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -99,7 +99,7 @@ def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWor def numberOfWorkers = Math.min(numberOfExecutions, maxWorkerProcesses) for(def i = 1; i <= numberOfWorkers; i++) { - def workerExecutions = numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0) + def workerExecutions = floor(numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0)) workerMap["agent-${agentNumber}-worker-${i}"] = { workerNumber -> for(def j = 0; j < workerExecutions; j++) { diff --git a/.ci/es-snapshots/Jenkinsfile_build_es b/.ci/es-snapshots/Jenkinsfile_build_es new file mode 100644 index 0000000000000..ad0ad54275e12 --- /dev/null +++ b/.ci/es-snapshots/Jenkinsfile_build_es @@ -0,0 +1,162 @@ +#!/bin/groovy + +// This job effectively has two SCM configurations: +// one for kibana, used to check out this Jenkinsfile (which means it's the job's main SCM configuration), as well as kick-off the downstream verification job +// one for elasticsearch, used to check out the elasticsearch source before building it + +// There are two parameters that drive which branch is checked out for each of these, but they will typically be the same +// 'branch_specifier' is for kibana / the job itself +// ES_BRANCH is for elasticsearch + +library 'kibana-pipeline-library' +kibanaLibrary.load() + +def ES_BRANCH = params.ES_BRANCH + +if (!ES_BRANCH) { + error "Parameter 'ES_BRANCH' must be specified." +} + +currentBuild.displayName += " - ${ES_BRANCH}" +currentBuild.description = "ES: ${ES_BRANCH}
Kibana: ${params.branch_specifier}" + +def PROMOTE_WITHOUT_VERIFY = !!params.PROMOTE_WITHOUT_VERIFICATION + +timeout(time: 120, unit: 'MINUTES') { + timestamps { + ansiColor('xterm') { + node('linux && immutable') { + catchError { + def VERSION + def SNAPSHOT_ID + def DESTINATION + + def scmVars = checkoutEs(ES_BRANCH) + def GIT_COMMIT = scmVars.GIT_COMMIT + def GIT_COMMIT_SHORT = sh(script: "git rev-parse --short ${GIT_COMMIT}", returnStdout: true).trim() + + buildArchives('to-archive') + + dir('to-archive') { + def now = new Date() + def date = now.format("yyyyMMdd-HHmmss") + + def filesRaw = sh(script: "ls -1", returnStdout: true).trim() + def files = filesRaw + .split("\n") + .collect { filename -> + // Filename examples + // elasticsearch-oss-8.0.0-SNAPSHOT-linux-x86_64.tar.gz + // elasticsearch-8.0.0-SNAPSHOT-linux-x86_64.tar.gz + def parts = filename.replace("elasticsearch-oss", "oss").split("-") + + VERSION = VERSION ?: parts[1] + SNAPSHOT_ID = SNAPSHOT_ID ?: "${date}_${GIT_COMMIT_SHORT}" + DESTINATION = DESTINATION ?: "${VERSION}/archives/${SNAPSHOT_ID}" + + return [ + filename: filename, + checksum: filename + '.sha512', + url: "https://storage.googleapis.com/kibana-ci-es-snapshots-daily/${DESTINATION}/${filename}".toString(), + version: parts[1], + platform: parts[3], + architecture: parts[4].split('\\.')[0], + license: parts[0] == 'oss' ? 'oss' : 'default', + ] + } + + sh 'find * -exec bash -c "shasum -a 512 {} > {}.sha512" \\;' + + def manifest = [ + bucket: "kibana-ci-es-snapshots-daily/${DESTINATION}".toString(), + branch: ES_BRANCH, + sha: GIT_COMMIT, + sha_short: GIT_COMMIT_SHORT, + version: VERSION, + generated: now.format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC")), + archives: files, + ] + def manifestJson = toJSON(manifest).toString() + writeFile file: 'manifest.json', text: manifestJson + + upload(DESTINATION, '*.*') + + sh "cp manifest.json manifest-latest.json" + upload(VERSION, 'manifest-latest.json') + } + + if (PROMOTE_WITHOUT_VERIFY) { + esSnapshots.promote(VERSION, SNAPSHOT_ID) + + emailext( + to: 'build-kibana@elastic.co', + subject: "ES snapshot promoted without verification: ${params.ES_BRANCH}", + body: '${SCRIPT,template="groovy-html.template"}', + mimeType: 'text/html', + ) + } else { + build( + propagate: false, + wait: false, + job: 'elasticsearch+snapshots+verify', + parameters: [ + string(name: 'branch_specifier', value: branch_specifier), + string(name: 'SNAPSHOT_VERSION', value: VERSION), + string(name: 'SNAPSHOT_ID', value: SNAPSHOT_ID), + ] + ) + } + } + + kibanaPipeline.sendMail() + } + } + } +} + +def checkoutEs(branch) { + retryWithDelay(8, 15) { + return checkout([ + $class: 'GitSCM', + branches: [[name: branch]], + doGenerateSubmoduleConfigurations: false, + extensions: [], + submoduleCfg: [], + userRemoteConfigs: [[ + credentialsId: 'f6c7695a-671e-4f4f-a331-acdce44ff9ba', + url: 'git@github.com:elastic/elasticsearch', + ]], + ]) + } +} + +def upload(destination, pattern) { + return googleStorageUpload( + credentialsId: 'kibana-ci-gcs-plugin', + bucket: "gs://kibana-ci-es-snapshots-daily/${destination}", + pattern: pattern, + sharedPublicly: false, + showInline: false, + ) +} + +def buildArchives(destination) { + def props = readProperties file: '.ci/java-versions.properties' + withEnv([ + // Select the correct JDK for this branch + "PATH=/var/lib/jenkins/.java/${props.ES_BUILD_JAVA}/bin:${env.PATH}", + + // These Jenkins env vars trigger some automation in the elasticsearch repo that we don't want + "BUILD_NUMBER=", + "JENKINS_URL=", + "BUILD_URL=", + "JOB_NAME=", + "NODE_NAME=", + ]) { + sh """ + ./gradlew -p distribution/archives assemble --parallel + mkdir -p ${destination} + find distribution/archives -type f \\( -name 'elasticsearch-*-*-*-*.tar.gz' -o -name 'elasticsearch-*-*-*-*.zip' \\) -not -path *no-jdk* -exec cp {} ${destination} \\; + """ + } +} diff --git a/.ci/es-snapshots/Jenkinsfile_trigger_build_es b/.ci/es-snapshots/Jenkinsfile_trigger_build_es new file mode 100644 index 0000000000000..186917e967824 --- /dev/null +++ b/.ci/es-snapshots/Jenkinsfile_trigger_build_es @@ -0,0 +1,19 @@ +#!/bin/groovy + +if (!params.branches_yaml) { + error "'branches_yaml' parameter must be specified" +} + +def branches = readYaml text: params.branches_yaml + +branches.each { branch -> + build( + propagate: false, + wait: false, + job: 'elasticsearch+snapshots+build', + parameters: [ + string(name: 'branch_specifier', value: branch), + string(name: 'ES_BRANCH', value: branch), + ] + ) +} diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es new file mode 100644 index 0000000000000..3d5ec75fa0e72 --- /dev/null +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -0,0 +1,72 @@ +#!/bin/groovy + +library 'kibana-pipeline-library' +kibanaLibrary.load() + +def SNAPSHOT_VERSION = params.SNAPSHOT_VERSION +def SNAPSHOT_ID = params.SNAPSHOT_ID + +if (!SNAPSHOT_VERSION) { + error "Parameter SNAPSHOT_VERSION must be specified" +} + +if (!SNAPSHOT_ID) { + error "Parameter SNAPSHOT_ID must be specified" +} + +currentBuild.displayName += " - ${SNAPSHOT_VERSION}" +currentBuild.description = "ES: ${SNAPSHOT_VERSION}
Kibana: ${params.branch_specifier}" + +def SNAPSHOT_MANIFEST = "https://storage.googleapis.com/kibana-ci-es-snapshots-daily/${SNAPSHOT_VERSION}/archives/${SNAPSHOT_ID}/manifest.json" + +timeout(time: 120, unit: 'MINUTES') { + timestamps { + ansiColor('xterm') { + catchError { + withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) { + parallel([ + // TODO we just need to run integration tests from intake? + '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), + ]), + '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), + ]), + ]) + } + + promoteSnapshot(SNAPSHOT_VERSION, SNAPSHOT_ID) + } + + kibanaPipeline.sendMail() + } + } +} + +def promoteSnapshot(snapshotVersion, snapshotId) { + node('linux && immutable') { + esSnapshots.promote(snapshotVersion, snapshotId) + } +} diff --git a/.eslintrc.js b/.eslintrc.js index 03a674993ab50..bdab4e0cebbe5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -82,13 +82,6 @@ module.exports = { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['src/legacy/core_plugins/kibana/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/rules-of-hooks': 'off', - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/legacy/core_plugins/tile_map/**/*.{js,ts,tsx}'], rules: { @@ -101,24 +94,12 @@ module.exports = { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['src/legacy/core_plugins/vis_type_metric/**/*.{js,ts,tsx}'], - rules: { - 'jsx-a11y/click-events-have-key-events': 'off', - }, - }, { files: ['src/legacy/core_plugins/vis_type_table/**/*.{js,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['src/legacy/core_plugins/vis_type_vega/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/legacy/ui/public/vis/**/*.{js,ts,tsx}'], rules: { @@ -177,12 +158,6 @@ module.exports = { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['x-pack/legacy/plugins/monitoring/**/*.{js,ts,tsx}'], - rules: { - 'jsx-a11y/click-events-have-key-events': 'off', - }, - }, { files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,ts,tsx}'], rules: { @@ -253,6 +228,7 @@ module.exports = { '!x-pack/test/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'src/core/(public|server)/**/*', + 'examples/**/*', ], from: [ 'src/core/public/**/*', @@ -289,11 +265,15 @@ module.exports = { 'x-pack/legacy/plugins/**/*', '!x-pack/legacy/plugins/*/server/**/*', '!x-pack/legacy/plugins/*/index.{js,ts,tsx}', + + 'examples/**/*', + '!examples/**/server/**/*', ], from: [ 'src/core/server', 'src/core/server/**/*', '(src|x-pack)/plugins/*/server/**/*', + 'examples/**/server/**/*', ], errorMessage: 'Server modules cannot be imported into client modules or shared modules.', @@ -729,15 +709,13 @@ module.exports = { 'no-unreachable': 'error', 'no-unsafe-finally': 'error', 'no-useless-call': 'error', - // This will be turned on after bug fixes are mostly complete - // 'no-useless-catch': 'warn', + 'no-useless-catch': 'error', 'no-useless-concat': 'error', 'no-useless-computed-key': 'error', // This will be turned on after bug fixes are mostly complete // 'no-useless-escape': 'warn', 'no-useless-rename': 'error', - // This will be turned on after bug fixes are mostly complete - // 'no-useless-return': 'warn', + 'no-useless-return': 'error', // This will be turned on after bug fixers are mostly complete // 'no-void': 'warn', 'one-var-declaration-per-line': 'error', @@ -745,14 +723,13 @@ module.exports = { 'prefer-promise-reject-errors': 'error', 'prefer-rest-params': 'error', 'prefer-spread': 'error', - // This style will be turned on after most bugs are fixed - // 'prefer-template': 'warn', + 'prefer-template': 'error', 'react/boolean-prop-naming': 'error', 'react/button-has-type': 'error', + 'react/display-name': 'error', 'react/forbid-dom-props': 'error', 'react/no-access-state-in-setstate': 'error', - // This style will be turned on after most bugs are fixed - // 'react/no-children-prop': 'warn', + 'react/no-children-prop': 'error', 'react/no-danger-with-children': 'error', 'react/no-deprecated': 'error', 'react/no-did-mount-set-state': 'error', @@ -814,21 +791,6 @@ module.exports = { }, }, - /** - * Monitoring overrides - */ - { - files: ['x-pack/legacy/plugins/monitoring/**/*.js'], - rules: { - 'no-unused-vars': ['error', { args: 'all', argsIgnorePattern: '^_' }], - 'no-else-return': 'error', - }, - }, - { - files: ['x-pack/legacy/plugins/monitoring/public/**/*.js'], - env: { browser: true }, - }, - /** * Canvas overrides */ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1137fb99f81a7..a0a22446ba31d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,7 @@ /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/home/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app +/src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/plugins/home/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app @@ -30,6 +31,7 @@ /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/elasticsearch/lib/create_proxy.js @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 @@ -146,6 +148,3 @@ /x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui /x-pack/legacy/plugins/watcher/ @elastic/es-ui - -# Kibana TSVB external contractors -/src/legacy/core_plugins/metrics/ @elastic/kibana-tsvb-external diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06e08c85dafec..6ae3db559b61b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,6 +190,19 @@ These snapshots are built on a nightly basis which expire after a couple weeks. yarn es snapshot ``` +##### Keeping data between snapshots + +If you want to keep the data inside your Elasticsearch between usages of this command, +you should use the following command, to keep your data folder outside the downloaded snapshot +folder: + +```bash +yarn es snapshot -E path.data=../data +``` + +The same parameter can be used with the source and archive command shown in the following +paragraphs. + #### Source By default, it will reference an [elasticsearch](https://github.com/elastic/elasticsearch) checkout which is a sibling to the Kibana directory named `elasticsearch`. If you wish to use a checkout in another location you can provide that by supplying `--source-path` diff --git a/docs/apm/settings.asciidoc b/docs/apm/settings.asciidoc index 2fc8748f13b09..37122fc9c635d 100644 --- a/docs/apm/settings.asciidoc +++ b/docs/apm/settings.asciidoc @@ -3,8 +3,16 @@ [[apm-settings-in-kibana]] === APM settings in Kibana -You do not need to configure any settings to use APM. It is enabled by default. -If you'd like to change any of the default values, -copy and paste the relevant settings below into your `kibana.yml` configuration file. +You do not need to configure any settings to use the APM app. It is enabled by default. + +[float] +[[apm-indices-settings]] +==== APM Indices + +include::./../settings/apm-settings.asciidoc[tag=apm-indices-settings] + +[float] +[[general-apm-settings]] +==== General APM settings include::./../settings/apm-settings.asciidoc[tag=general-apm-settings] diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index ec0863b09d653..22279b69b70fe 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -17,6 +17,7 @@ This section can help with any of the following: There are a number of factors that could be at play here. One important thing to double-check first is your index template. +*Index template* An APM index template must exist for the APM app to work correctly. By default, this index template is created by APM Server on startup. However, this only happens if `setup.template.enabled` is `true` in `apm-server.yml`. @@ -34,14 +35,21 @@ GET /_template/apm-{version} -------------------------------------------------- // CONSOLE +*Using Logstash, Kafka, etc.* If you're not outputting data directly from APM Server to Elasticsearch (perhaps you're using Logstash or Kafka), then the index template will not be set up automatically. Instead, you'll need to -{apm-server-ref}/_manually_loading_template_configuration.html#load-template-manually-alternate[load the template manually]. +{apm-server-ref}/_manually_loading_template_configuration.html[load the template manually]. -Finally, this problem can also occur if you've changed the index name that you write APM data to. -The default index pattern can be found {apm-server-ref}/elasticsearch-output.html#index-option-es[here]. -If you change this setting, you must also configure the `setup.template.name` and `setup.template.pattern` options. +*Using a custom index names* +This problem can also occur if you've customized the index name that you write APM data to. +The default index name that APM writes events to can be found +{apm-server-ref}/elasticsearch-output.html#index-option-es[here]. +If you change the default, you must also configure the `setup.template.name` and `setup.template.pattern` options. See {apm-server-ref}/configuration-template.html[Load the Elasticsearch index template]. +If the Elasticsearch index template has already been successfully loaded to the index, +you can customize the indices that the APM app uses to display data. +Navigate to *APM* > *Settings* > *Indices*, and change all `apm_oss.*Pattern` values to +include the new index pattern. For example: `customIndexName-*`. ==== Unknown route diff --git a/docs/developer/plugin/development-uiexports.asciidoc b/docs/developer/plugin/development-uiexports.asciidoc index 6368446f7fb43..18d326cbfb9c0 100644 --- a/docs/developer/plugin/development-uiexports.asciidoc +++ b/docs/developer/plugin/development-uiexports.asciidoc @@ -9,8 +9,8 @@ An aggregate list of available UiExport types: | hacks | Any module that should be included in every application | visTypes | Modules that register providers with the `ui/registry/vis_types` registry. | inspectorViews | Modules that register custom inspector views via the `viewRegistry` in `ui/inspector`. -| chromeNavControls | Modules that register providers with the `ui/registry/chrome_nav_controls` registry. -| navbarExtensions | Modules that register providers with the `ui/registry/navbar_extensions` registry. -| docViews | Modules that register providers with the `ui/registry/doc_views` registry. +| chromeNavControls | Modules that register providers with the `ui/registry/chrome_header_nav_controls` registry. +| navbarExtensions | Modules that register providers with the setup contract of the `navigation` plugin. +| docViews | Modules that register providers with the setup contract method `addDocView` of the `discover` plugin. | app | Adds an application to the system. This uiExport type is defined as an object of metadata rather than just a module id. |======================================================================= 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 1ce18834f5319..a4fa3f17d0d94 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 3b916db972673..88485aa71f7c5 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -24,7 +24,7 @@ The constructor for this class is marked as internal. Third-party code should no | [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" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => 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/server/kibana-plugin-server.clusterclient.asscoped.md b/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md index ed7d028a1ec8a..bb1f481c9ef4f 100644 --- a/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md +++ b/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md @@ -9,14 +9,14 @@ Creates an instance of [IScopedClusterClient](./kibana-plugin-server.iscopedclus Signature: ```typescript -asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; +asScoped(request?: ScopeableRequest): IScopedClusterClient; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| request | KibanaRequest | LegacyRequest | FakeRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | +| request | ScopeableRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.clusterclient.md b/docs/development/core/server/kibana-plugin-server.clusterclient.md index 5fdda7ef3e499..d547b846e65b7 100644 --- a/docs/development/core/server/kibana-plugin-server.clusterclient.md +++ b/docs/development/core/server/kibana-plugin-server.clusterclient.md @@ -4,7 +4,7 @@ ## ClusterClient class -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(...)`). +Represents an Elasticsearch cluster API client created by the platform. It 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). diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient.md new file mode 100644 index 0000000000000..415423f555266 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [adminClient](./kibana-plugin-server.elasticsearchservicesetup.adminclient.md) + +## ElasticsearchServiceSetup.adminClient property + +A client for the `admin` cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). + +Signature: + +```typescript +readonly adminClient: IClusterClient; +``` + +## Example + + +```js +const client = core.elasticsearch.adminClient; + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md deleted file mode 100644 index b5bfc68d3ca0c..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [adminClient$](./kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) - -## ElasticsearchServiceSetup.adminClient$ property - -Observable of clients for the `admin` cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). - - -```js -const client = await elasticsearch.adminClient$.pipe(take(1)).toPromise(); - -``` - -Signature: - -```typescript -readonly adminClient$: Observable; -``` diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md index 3d26f2d4cec88..797f402cc2580 100644 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md @@ -9,7 +9,7 @@ Create application specific Elasticsearch cluster API client with customized con Signature: ```typescript -readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; +readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient.md new file mode 100644 index 0000000000000..e9845dce6915d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [dataClient](./kibana-plugin-server.elasticsearchservicesetup.dataclient.md) + +## ElasticsearchServiceSetup.dataClient property + +A client for the `data` cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). + +Signature: + +```typescript +readonly dataClient: IClusterClient; +``` + +## Example + + +```js +const client = core.elasticsearch.dataClient; + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md deleted file mode 100644 index 9411f2f6b8694..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [dataClient$](./kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) - -## ElasticsearchServiceSetup.dataClient$ property - -Observable of clients for the `data` cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). - - -```js -const client = await elasticsearch.dataClient$.pipe(take(1)).toPromise(); - -``` - -Signature: - -```typescript -readonly dataClient$: Observable; -``` diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md index e3d151cdc0d8b..2de3f6e6d1bbc 100644 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md @@ -15,17 +15,7 @@ export interface ElasticsearchServiceSetup | Property | Type | Description | | --- | --- | --- | -| [adminClient$](./kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Observable<IClusterClient> | Observable of clients for the admin cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). -```js -const client = await elasticsearch.adminClient$.pipe(take(1)).toPromise(); - -``` - | -| [createClient](./kibana-plugin-server.elasticsearchservicesetup.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => IClusterClient | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | -| [dataClient$](./kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Observable<IClusterClient> | Observable of clients for the data cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). -```js -const client = await elasticsearch.dataClient$.pipe(take(1)).toPromise(); - -``` - | +| [adminClient](./kibana-plugin-server.elasticsearchservicesetup.adminclient.md) | IClusterClient | A client for the admin cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | +| [createClient](./kibana-plugin-server.elasticsearchservicesetup.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | +| [dataClient](./kibana-plugin-server.elasticsearchservicesetup.dataclient.md) | IClusterClient | A client for the data cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | diff --git a/docs/development/core/server/kibana-plugin-server.iclusterclient.md b/docs/development/core/server/kibana-plugin-server.iclusterclient.md index 834afa6db5157..e7435a9d91a74 100644 --- a/docs/development/core/server/kibana-plugin-server.iclusterclient.md +++ b/docs/development/core/server/kibana-plugin-server.iclusterclient.md @@ -4,12 +4,12 @@ ## IClusterClient type -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(...)`). +Represents an Elasticsearch cluster API client created by the platform. It 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). Signature: ```typescript -export declare type IClusterClient = Pick; +export declare type IClusterClient = Pick; ``` diff --git a/docs/development/core/server/kibana-plugin-server.icustomclusterclient.md b/docs/development/core/server/kibana-plugin-server.icustomclusterclient.md new file mode 100644 index 0000000000000..bc9ea71bc2389 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icustomclusterclient.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) + +## ICustomClusterClient type + +Represents an Elasticsearch cluster API client created by a plugin.. It 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). + +Signature: + +```typescript +export declare type ICustomClusterClient = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 5e7f84c55244d..3f01048846fe1 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -17,7 +17,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Class | Description | | --- | --- | | [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | -| [ClusterClient](./kibana-plugin-server.clusterclient.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). | +| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It 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). | | [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | @@ -175,8 +175,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | | [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | -| [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). | +| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It 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. | +| [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin.. It 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). | | [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). | @@ -213,6 +214,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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. | +| [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-server.kibanarequest.md). | | [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) | | | [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.scopeablerequest.md b/docs/development/core/server/kibana-plugin-server.scopeablerequest.md new file mode 100644 index 0000000000000..5a9443376996d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.scopeablerequest.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) + +## ScopeableRequest type + +A user credentials container. It accommodates the necessary auth credentials to impersonate the current user. + +See [KibanaRequest](./kibana-plugin-server.kibanarequest.md). + +Signature: + +```typescript +export declare type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; +``` diff --git a/docs/limitations.asciidoc b/docs/limitations.asciidoc index 9bcba3b65d660..818cc766bf6a9 100644 --- a/docs/limitations.asciidoc +++ b/docs/limitations.asciidoc @@ -19,4 +19,4 @@ These {stack} features also have limitations that affect {kib}: include::limitations/nested-objects.asciidoc[] -include::limitations/export-data.asciidoc[] +include::limitations/export-data.asciidoc[] \ No newline at end of file diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 8d9ef515108ed..8e687f641c92b 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -1,17 +1,22 @@ [[index-patterns]] -== Index patterns +== Creating an index pattern -To visualize and explore data in {kib}, you must create an index pattern. -An index pattern tells {kib} which {es} indices contain the data that you want to work with. -An index pattern can match a single index, multiple indices, and a rollup index. +To explore and visualize data in {kib}, you must create an index pattern. +An index pattern tells {kib} which {es} indices contain the data that +you want to work with. +Once you create an index pattern, you're ready to: + +* Interactively explore your data in <>. +* Analyze your data in charts, tables, gauges, tag clouds, and more in <>. +* Show off your data in a <> workpad. +* If your data includes geo data, visualize it with <>. [float] [[index-patterns-read-only-access]] === [xpack]#Read-only access# -If you have insufficient privileges to create or save index patterns, a read-only +If you have insufficient privileges to create or save index patterns, a read-only indicator appears in Kibana. The buttons to create new index patterns or save -existing index patterns are not visible. For more information on granting access to -Kibana see <>. +existing index patterns are not visible. For more information, see <>. [role="screenshot"] image::images/management-index-read-only-badge.png[Example of Index Pattern Management's read only access indicator in Kibana's header] @@ -20,12 +25,9 @@ image::images/management-index-read-only-badge.png[Example of Index Pattern Mana [[settings-create-pattern]] === Create an index pattern -To get started, go to *Management > Kibana > Index Patterns*. You begin with -an overview of your index patterns, including any that were added when you -downloaded sample data sets. - -You can create a standard index pattern, and if a rollup index is detected in the -cluster, a rollup index pattern. +If you are in an app that requires an index pattern, and you don't have one yet, +{kib} prompts you to create one. Or, you can go directly to +*Management > Kibana > Index Patterns*. [role="screenshot"] image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollup index pattern"] @@ -33,83 +35,93 @@ image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollu [float] ==== Standard index pattern -{kib} makes it easy for you to create an index pattern by walking you through -the process. Just start typing in the *Index pattern* field, and {kib} looks for -the names of {es} indices that match your input. Make sure that the name of the +Just start typing in the *Index pattern* field, and {kib} looks for +the names of {es} indices that match your input. Make sure that the name of the index pattern is unique. - -If you want to include system indices in your search, toggle the switch in the -upper right. +To include system indices in your search, toggle the switch in the upper right. [role="screenshot"] image:management/index-patterns/images/create-index-pattern.png["Create index pattern"] -Your index pattern can match multiple {es} indices. -Use a comma to separate the names, with no space after the comma. The notation for -wildcards (`*`) and the ability to "exclude" (`-`) also apply +Your index pattern can match multiple {es} indices. +Use a comma to separate the names, with no space after the comma. The notation for +wildcards (`*`) and the ability to "exclude" (`-`) also apply (for example, `test*,-test3`). -When {kib} detects an index with a timestamp, you’re asked to choose a field to -filter your data by time. If you don’t specify a field, you won’t be able +If {kib} detects an index with a timestamp, you’re asked to choose a field to +filter your data by time. If you don’t specify a field, you won’t be able to use the time filter. -Once you’ve created your index pattern, you can start working with -your {es} data in {kib}. Here are some things to try: -* Interactively explore your data in <>. -* Present your data in charts, tables, gauges, tag clouds, and more in <>. -* Show off your data in a <> presentation. -* If your data includes geo data, visualize it using <>. - -For a walkthrough of creating an index pattern and visualizing the data, -see <>. [float] ==== Rollup index pattern -If a rollup index is detected in the cluster, clicking *Create index pattern* -includes an item for creating a rollup index pattern. You create an -index pattern for rolled up data the same way you do for any data. +If a rollup index is detected in the cluster, clicking *Create index pattern* +includes an item for creating a rollup index pattern. +You can match an index pattern to only rolled up data, or mix both rolled +up and raw data to explore and visualize all data together. +An index pattern can match +only one rollup index. + +[float] +[[management-cross-cluster-search]] +==== {ccs-cap} index pattern + +If your {es} clusters are configured for {ref}/modules-cross-cluster-search.html[{ccs}], you can create +index patterns to search across the clusters of your choosing. Using the +same syntax that you'd use in a raw {ccs} request in {es}, create your +index pattern with the convention `:`. + +For example, to query {ls} indices across two {es} clusters +that you set up for {ccs}, which are named `cluster_one` and `cluster_two`, +you would use `cluster_one:logstash-*,cluster_two:logstash-*` as your index pattern. + +You can use wildcards in your cluster names +to match any number of clusters, so if you want to search {ls} indices across +clusters named `cluster_foo`, `cluster_bar`, and so on, you would use `cluster_*:logstash-*` +as your index pattern. -You can match an index pattern to only rolled up data, or mix both rolled -up and raw data to visualize all data together. An index pattern can match -only one rollup index, not multiple. There is no restriction on the -number of standard indices that an index pattern can match. +To query across all {es} clusters that have been configured for {ccs}, +use a standalone wildcard for your cluster name in your index +pattern: `*:logstash-*`. -See <> -for more detailed information. +Once an index pattern is configured using the {ccs} syntax, all searches and +aggregations using that index pattern in {kib} take advantage of {ccs}. [float] === Manage your index pattern -Once you’ve created an index pattern, you’re presented a table of all fields -and associated data types in the index. +Once you create an index pattern, manually or with a sample data set, +you can look at its fields and associated data types. +You can also perform housekeeping tasks, such as making the +index pattern the default or deleting it when you longer need it. +To drill down into the details of an index pattern, click its name in +the *Index patterns* overview. [role="screenshot"] image:management/index-patterns/images/new-index-pattern.png["Index files and data types"] -You can perform the following actions: +From the detailed view, you can perform the following actions: -* *Manage the index fields.* Click a column header to sort the table by that column. -Use the field dropdown menu to limit to display to a specific field. -See <> for more detailed information. +* *Manage the index fields.* You can add formatters to format values and create +scripted fields. +See <> for more information. -* [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users -aware of which index pattern is the default. The first pattern -you create is automatically designated as the default pattern. The default -index pattern is loaded when you view the Discover tab. +* [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users +aware of which index pattern is the default. The first pattern +you create is automatically designated as the default pattern. The default +index pattern is loaded when you open *Discover*. -* [[reload-fields]]*Reload the index fields list.* You can reload the index fields list to -pick up any newly-added fields. Doing so also resets Kibana’s popularity counters -for the fields. The popularity counters keep track of the fields -you’ve used most often in {kib} and are used to sort fields in lists. +* [[reload-fields]]*Refresh the index fields list.* You can refresh the index fields list to +pick up any newly-added fields. Doing so also resets Kibana’s popularity counters +for the fields. The popularity counters are used in *Discover* to sort fields in lists. -* [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of -Saved Objects in {kib}. You will not be able to recover field formatters, +* [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of +Saved Objects in {kib}. You will not be able to recover field formatters, scripted fields, source filters, and field popularity data associated with the index pattern. -+ -Deleting an index pattern breaks all visualizations, saved searches, and -other saved objects that reference the pattern. Deleting an index pattern does +Deleting an index pattern does not remove any indices or data documents from {es}. - -include::index-patterns/management-cross-cluster-search.asciidoc[] ++ +WARNING: Deleting an index pattern breaks all visualizations, saved searches, and +other saved objects that reference the pattern. diff --git a/docs/management/index-patterns/management-cross-cluster-search.asciidoc b/docs/management/index-patterns/management-cross-cluster-search.asciidoc deleted file mode 100644 index 9fd8deb7f34be..0000000000000 --- a/docs/management/index-patterns/management-cross-cluster-search.asciidoc +++ /dev/null @@ -1,30 +0,0 @@ -[[management-cross-cluster-search]] -=== {ccs-cap} - -{es} supports the ability to run search and aggregation requests across multiple -clusters using a module called _{ccs}_. - -In order to take advantage of {ccs}, you must configure your {es} -clusters accordingly. Review the corresponding {es} -{ref}/modules-cross-cluster-search.html[documentation] before attempting to use {ccs} in {kib}. - -Once your {es} clusters are configured for {ccs}, you can create -specific index patterns in {kib} to search across the clusters of your choosing. Using the -same syntax that you'd use in a raw {ccs} request in {es}, create your -index pattern in {kib} with the convention `:`. - -For example, if you want to query {ls} indices across two of the {es} clusters -that you set up for {ccs}, which were named `cluster_one` and `cluster_two`, -you would use `cluster_one:logstash-*,cluster_two:logstash-*` as your index pattern in {kib}. - -Just like in raw search requests in {es}, you can use wildcards in your cluster names -to match any number of clusters, so if you wanted to search {ls} indices across any -clusters named `cluster_foo`, `cluster_bar`, and so on, you would use `cluster_*:logstash-*` -as your index pattern in {kib}. - -If you want to query across all {es} clusters that have been configured for {ccs}, -then use a standalone wildcard for your cluster name in your {kib} index -pattern: `*:logstash-*`. - -Once an index pattern is configured using the {ccs} syntax, all searches and -aggregations using that index pattern in {kib} take advantage of {ccs}. diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 8d28b55a6502f..a6eeffec51cb0 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -5,9 +5,23 @@ APM settings ++++ -You do not need to configure any settings to use APM. It is enabled by default. -If you'd like to change any of the default values, -copy and paste the relevant settings below into your `kibana.yml` configuration file. +You do not need to configure any settings to use the APM app. It is enabled by default. + +[float] +[[apm-indices-settings-kb]] +==== APM Indices + +// This content is reused in the APM app documentation. +// Any changes made in this file will be seen there as well. +// tag::apm-indices-settings[] + +Index defaults can be changed in Kibana. Navigate to *APM* > *Settings* > *Indices*. +Index settings in the APM app take precedence over those set in `kibana.yml`. + +[role="screenshot"] +image::settings/images/apm-settings.png[APM app settings in Kibana] + +// end::apm-indices-settings[] [float] [[general-apm-settings-kb]] @@ -17,6 +31,9 @@ copy and paste the relevant settings below into your `kibana.yml` configuration // Any changes made in this file will be seen there as well. // tag::general-apm-settings[] +If you'd like to change any of the default values, +copy and paste the relevant settings below into your `kibana.yml` configuration file. + xpack.apm.enabled:: Set to `false` to disabled the APM plugin {kib}. Defaults to `true`. diff --git a/docs/settings/images/apm-settings.png b/docs/settings/images/apm-settings.png new file mode 100644 index 0000000000000..876f135da9356 Binary files /dev/null and b/docs/settings/images/apm-settings.png differ diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 2fc74d2ffee32..38a46a3cde5a0 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -66,13 +66,6 @@ both the {es} monitoring cluster and the {es} production cluster. If not set, {kib} uses the value of the `elasticsearch.password` setting. -`telemetry.enabled`:: -Set to `true` (default) to send cluster statistics to Elastic. Reporting your -cluster statistics helps us improve your user experience. Your data is never -shared with anyone. Set to `false` to disable statistics reporting from any -browser connected to the {kib} instance. You can also opt out through the -*Advanced Settings* in {kib}. - `xpack.monitoring.elasticsearch.pingTimeout`:: Specifies the time in milliseconds to wait for {es} to respond to internal health checks. By default, it matches the `elasticsearch.pingTimeout` setting, diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 01e6bd51ea50b..c98df6ca78356 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -380,6 +380,11 @@ cannot be `false` at the same time. To enable telemetry and prevent users from disabling it, set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. +`telemetry.enabled`:: *Default: true* Reporting your cluster statistics helps +us improve your user experience. Your data is never shared with anyone. Set to +`false` to disable telemetry capabilities entirely. You can alternatively opt +out through the *Advanced Settings* in {kib}. + `vega.enableExternalUrls:`:: *Default: false* Set this value to true to allow Vega to use any URL to access external data sources and images. If false, Vega can only get data from Elasticsearch. `xpack.license_management.enabled`:: *Default: true* Set this value to false to diff --git a/docs/spaces/images/spaces-configure-landing-page.png b/docs/spaces/images/spaces-configure-landing-page.png new file mode 100644 index 0000000000000..15006594b6d7b Binary files /dev/null and b/docs/spaces/images/spaces-configure-landing-page.png differ diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 69655aac521e7..fb5ef670692dc 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -2,13 +2,13 @@ [[xpack-spaces]] == Spaces -Spaces enable you to organize your dashboards and other saved -objects into meaningful categories. Once inside a space, you see only -the dashboards and saved objects that belong to that space. +Spaces enable you to organize your dashboards and other saved +objects into meaningful categories. Once inside a space, you see only +the dashboards and saved objects that belong to that space. -{kib} creates a default space for you. -After you create your own -spaces, you're asked to choose a space when you log in to Kibana. You can change your +{kib} creates a default space for you. +After you create your own +spaces, you're asked to choose a space when you log in to Kibana. You can change your current space at any time by using the menu in the upper left. [role="screenshot"] @@ -29,24 +29,24 @@ Kibana supports spaces in several ways. You can: [[spaces-managing]] === View, create, and delete spaces -Go to **Management > Spaces** for an overview of your spaces. This view provides actions +Go to **Management > Spaces** for an overview of your spaces. This view provides actions for you to create, edit, and delete spaces. [role="screenshot"] image::spaces/images/space-management.png["Space management"] [float] -==== Create or edit a space +==== Create or edit a space -You can create as many spaces as you like. Click *Create a space* and provide a name, -URL identifier, optional description. +You can create as many spaces as you like. Click *Create a space* and provide a name, +URL identifier, optional description. -The URL identifier is a short text string that becomes part of the -{kib} URL when you are inside that space. {kib} suggests a URL identifier based +The URL identifier is a short text string that becomes part of the +{kib} URL when you are inside that space. {kib} suggests a URL identifier based on the name of your space, but you can customize the identifier to your liking. You cannot change the space identifier once you create the space. -{kib} also has an <> +{kib} also has an <> if you prefer to create spaces programatically. [role="screenshot"] @@ -55,7 +55,7 @@ image::spaces/images/edit-space.png["Space management"] [float] ==== Delete a space -Deleting a space permanently removes the space and all of its contents. +Deleting a space permanently removes the space and all of its contents. Find the space on the *Spaces* overview page and click the trash icon in the Actions column. You can't delete the default space, but you can customize it to your liking. @@ -63,14 +63,14 @@ You can't delete the default space, but you can customize it to your liking. [[spaces-control-feature-visibility]] === Control feature access based on user needs -You have control over which features are visible in each space. -For example, you might hide Dev Tools +You have control over which features are visible in each space. +For example, you might hide Dev Tools in your "Executive" space or show Stack Monitoring only in your "Admin" space. You can define which features to show or hide when you add or edit a space. -Controlling feature -visibility is not a security feature. To secure access -to specific features on a per-user basis, you must configure +Controlling feature +visibility is not a security feature. To secure access +to specific features on a per-user basis, you must configure <>. [role="screenshot"] @@ -80,10 +80,10 @@ image::spaces/images/edit-space-feature-visibility.png["Controlling features vis [[spaces-control-user-access]] === Control feature access based on user privileges -When using Kibana with security, you can configure applications and features -based on your users’ privileges. This means different roles can have access -to different features in the same space. -Power users might have privileges to create and edit visualizations and dashboards, +When using Kibana with security, you can configure applications and features +based on your users’ privileges. This means different roles can have access +to different features in the same space. +Power users might have privileges to create and edit visualizations and dashboards, while analysts or executives might have Dashboard and Canvas with read-only privileges. See <> for details. @@ -106,7 +106,7 @@ interface. . Import your saved objects. . (Optional) Delete objects in the export space that you no longer need. -{kib} also has beta <> and +{kib} also has beta <> and <> APIs if you want to automate this process. [float] @@ -115,17 +115,22 @@ interface. You can create a custom experience for users by configuring the {kib} landing page on a per-space basis. The landing page can route users to a specific dashboard, application, or saved object as they enter each space. -To configure the landing page, use the `defaultRoute` setting in < Advanced settings>>. + +To configure the landing page, use the default route setting in < Advanced settings>>. +For example, you might set the default route to `/app/kibana#/dashboards`. + +[role="screenshot"] +image::spaces/images/spaces-configure-landing-page.png["Configure space-level landing page"] + [float] [[spaces-delete-started]] === Disable and version updates -Spaces are automatically enabled in {kib}. If you don't want use this feature, +Spaces are automatically enabled in {kib}. If you don't want use this feature, you can disable it -by setting `xpack.spaces.enabled` to `false` in your +by setting `xpack.spaces.enabled` to `false` in your `kibana.yml` configuration file. -If you are upgrading your -version of {kib}, the default space will contain all of your existing saved objects. - +If you are upgrading your +version of {kib}, the default space will contain all of your existing saved objects. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 36d6b0a6e473a..7de7d73bf1664 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -3,6 +3,7 @@ [partintro] -- + When you know what your data includes, you can create visualizations that best display that data and build better dashboards. *Discover* enables you to explore your data, find @@ -99,6 +100,8 @@ or create a direct link to share. The *Save* and *Share* actions are in the men -- +include::{kib-repo-dir}/management/index-patterns.asciidoc[] + include::{kib-repo-dir}/discover/set-time-filter.asciidoc[] include::{kib-repo-dir}/discover/search.asciidoc[] diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index d1acb915f1973..2c41d0072fe5b 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -13,8 +13,6 @@ visualizations, and dashboards. include::{kib-repo-dir}/management/managing-licenses.asciidoc[] -include::{kib-repo-dir}/management/index-patterns.asciidoc[] - include::{kib-repo-dir}/management/rollups/create_and_manage_rollups.asciidoc[] include::{kib-repo-dir}/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc[] @@ -40,5 +38,3 @@ include::{kib-repo-dir}/management/managing-beats.asciidoc[] include::{kib-repo-dir}/management/managing-remote-clusters.asciidoc[] include::{kib-repo-dir}/management/snapshot-restore/index.asciidoc[] - - diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index 86599be9af375..c2ed295e83ce9 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -57,6 +57,30 @@ that provides read and write privileges in Go to *Management > Users*, add a new user, and assign the user the built-in `reporting_user` role and your new custom role, `custom_reporting_user`. +[float] +==== With a custom index + +If you are using Reporting with a custom index, +the `xpack.reporting.index` setting should begin +with `.reporting-*`. The default {kib} system user has +`all` privileges against the `.reporting-*` pattern of indices. + +[source,js] +xpack.reporting.index: '.reporting-custom-index' + +If you use a different pattern for the `xpack.reporting.index` setting, +you must create a custom role with appropriate access to the index, similar +to the following: + +. Go to *Management > Roles*, and click *Create role*. +. Name the role `custom-reporting-user`. +. Specify the custom index and assign it the `all` index privilege. +. Go to *Management > Users* and create a new user with +the `kibana_system` role and the `custom-reporting-user` role. +. Configure {kib} to use the new account: +[source,js] +elasticsearch.username: 'custom_kibana_system' + [float] [[reporting-roles-user-api]] ==== With the user API diff --git a/examples/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts index 23c82225563c8..653aa217717fa 100644 --- a/examples/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; -import { DataPluginSetup } from 'src/plugins/data/server/plugin'; +import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server'; import { demoSearchStrategyProvider } from './demo_search_strategy'; import { DEMO_SEARCH_STRATEGY, IDemoRequest, IDemoResponse } from '../common'; diff --git a/package.json b/package.json index 99151f33962c4..0dbed9e432e99 100644 --- a/package.json +++ b/package.json @@ -240,7 +240,7 @@ "react-use": "^13.13.0", "reactcss": "1.2.3", "redux": "4.0.0", - "redux-actions": "2.2.1", + "redux-actions": "2.6.5", "redux-thunk": "2.3.0", "regenerator-runtime": "^0.13.3", "regression": "2.0.1", @@ -314,7 +314,7 @@ "@types/delete-empty": "^2.0.0", "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.9.0", - "@types/eslint": "^6.1.2", + "@types/eslint": "^6.1.3", "@types/fetch-mock": "^7.3.1", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.1", @@ -341,6 +341,7 @@ "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", "@types/opn": "^5.1.0", + "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", @@ -353,7 +354,7 @@ "@types/react-router-dom": "^5.1.3", "@types/react-virtualized": "^9.18.7", "@types/redux": "^3.6.31", - "@types/redux-actions": "^2.2.1", + "@types/redux-actions": "^2.6.1", "@types/request": "^2.48.2", "@types/selenium-webdriver": "^4.0.5", "@types/semver": "^5.5.0", @@ -368,8 +369,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.12.0", - "@typescript-eslint/parser": "^2.12.0", + "@typescript-eslint/eslint-plugin": "^2.15.0", + "@typescript-eslint/parser": "^2.15.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -389,21 +390,21 @@ "enzyme-adapter-react-16": "^1.15.1", "enzyme-adapter-utils": "^1.12.1", "enzyme-to-json": "^3.4.3", - "eslint": "^6.5.1", - "eslint-config-prettier": "^6.4.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.9.0", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-ban": "^1.3.0", - "eslint-plugin-cypress": "^2.7.0", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.19.0", + "eslint-plugin-ban": "^1.4.0", + "eslint-plugin-cypress": "^2.8.1", + "eslint-plugin-import": "^2.19.1", + "eslint-plugin-jest": "^23.3.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-mocha": "^6.2.0", + "eslint-plugin-mocha": "^6.2.2", "eslint-plugin-no-unsanitized": "^3.0.2", - "eslint-plugin-node": "^10.0.0", + "eslint-plugin-node": "^11.0.0", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-prettier": "^3.1.1", - "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.1.2", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-hooks": "^2.3.0", "exit-hook": "^2.2.0", "faker": "1.1.0", "fetch-mock": "^7.3.9", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 04602d196a7f3..34b1b0fec376f 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,19 +15,19 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.12.0", - "@typescript-eslint/parser": "^2.12.0", + "@typescript-eslint/eslint-plugin": "^2.15.0", + "@typescript-eslint/parser": "^2.15.0", "babel-eslint": "^10.0.3", - "eslint": "^6.5.1", + "eslint": "^6.8.0", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-ban": "^1.3.0", + "eslint-plugin-ban": "^1.4.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.19.0", - "eslint-plugin-mocha": "^6.2.0", + "eslint-plugin-import": "^2.19.1", + "eslint-plugin-jest": "^23.3.0", + "eslint-plugin-mocha": "^6.2.2", "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.1.2" + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-hooks": "^2.3.0" } } diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index fd62f1b3c03b2..e6f3e60128983 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -156,6 +156,9 @@ __Usage:__ const valueSchema = schema.boolean({ defaultValue: false }); ``` +__Notes:__ +* The `schema.boolean()` also supports a string as input if it equals `'true'` or `'false'` (case-insensitive). + #### `schema.literal()` Validates input data as a [string](https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types), [numeric](https://www.typescriptlang.org/docs/handbook/advanced-types.html#numeric-literal-types) or boolean literal. @@ -397,7 +400,7 @@ const valueSchema = schema.byteSize({ min: '3kb' }); ``` __Notes:__ -* The string value for `schema.byteSize()` and its options supports the following prefixes: `b`, `kb`, `mb`, `gb` and `tb`. +* The string value for `schema.byteSize()` and its options supports the following optional suffixes: `b`, `kb`, `mb`, `gb` and `tb`. The default suffix is `b`. * The number value is treated as a number of bytes and hence should be a positive integer, e.g. `100` is equal to `'100b'`. * Currently you cannot specify zero bytes with a string format and should use number `0` instead. @@ -417,7 +420,7 @@ const valueSchema = schema.duration({ defaultValue: '70ms' }); ``` __Notes:__ -* The string value for `schema.duration()` supports the following prefixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `Y`. +* The string value for `schema.duration()` supports the following optional suffixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `Y`. The default suffix is `ms`. * The number value is treated as a number of milliseconds and hence should be a positive integer, e.g. `100` is equal to `'100ms'`. #### `schema.conditional()` diff --git a/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap b/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap index 1db6930062a9a..97e9082401b3d 100644 --- a/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap +++ b/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap @@ -1,9 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`#constructor throws if number of bytes is negative 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-1024]"`; +exports[`#constructor throws if number of bytes is negative 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-1024]."`; -exports[`#constructor throws if number of bytes is not safe 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]"`; +exports[`#constructor throws if number of bytes is not safe 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]."`; -exports[`#constructor throws if number of bytes is not safe 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]"`; +exports[`#constructor throws if number of bytes is not safe 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]."`; -exports[`#constructor throws if number of bytes is not safe 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]"`; +exports[`#constructor throws if number of bytes is not safe 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]."`; + +exports[`parsing units throws an error when unsupported unit specified 1`] = `"Failed to parse [1tb] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] (e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer."`; diff --git a/packages/kbn-config-schema/src/byte_size_value/index.test.ts b/packages/kbn-config-schema/src/byte_size_value/index.test.ts index 46ed96c83dd1f..198d95aa0ab4c 100644 --- a/packages/kbn-config-schema/src/byte_size_value/index.test.ts +++ b/packages/kbn-config-schema/src/byte_size_value/index.test.ts @@ -20,6 +20,10 @@ import { ByteSizeValue } from '.'; describe('parsing units', () => { + test('number string (bytes)', () => { + expect(ByteSizeValue.parse('123').getValueInBytes()).toBe(123); + }); + test('bytes', () => { expect(ByteSizeValue.parse('123b').getValueInBytes()).toBe(123); }); @@ -37,12 +41,8 @@ describe('parsing units', () => { expect(ByteSizeValue.parse('1gb').getValueInBytes()).toBe(1073741824); }); - test('throws an error when no unit specified', () => { - expect(() => ByteSizeValue.parse('123')).toThrowError('could not parse byte size value'); - }); - test('throws an error when unsupported unit specified', () => { - expect(() => ByteSizeValue.parse('1tb')).toThrowError('could not parse byte size value'); + expect(() => ByteSizeValue.parse('1tb')).toThrowErrorMatchingSnapshot(); }); }); diff --git a/packages/kbn-config-schema/src/byte_size_value/index.ts b/packages/kbn-config-schema/src/byte_size_value/index.ts index fb0105503a149..48862821bb78d 100644 --- a/packages/kbn-config-schema/src/byte_size_value/index.ts +++ b/packages/kbn-config-schema/src/byte_size_value/index.ts @@ -35,9 +35,14 @@ export class ByteSizeValue { public static parse(text: string): ByteSizeValue { const match = /([1-9][0-9]*)(b|kb|mb|gb)/.exec(text); if (!match) { - throw new Error( - `could not parse byte size value [${text}]. Value must be a safe positive integer.` - ); + const number = Number(text); + if (typeof number !== 'number' || isNaN(number)) { + throw new Error( + `Failed to parse [${text}] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] ` + + `(e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer.` + ); + } + return new ByteSizeValue(number); } const value = parseInt(match[1], 0); @@ -49,8 +54,7 @@ export class ByteSizeValue { constructor(private readonly valueInBytes: number) { if (!Number.isSafeInteger(valueInBytes) || valueInBytes < 0) { throw new Error( - `Value in bytes is expected to be a safe positive integer, ` + - `but provided [${valueInBytes}]` + `Value in bytes is expected to be a safe positive integer, but provided [${valueInBytes}].` ); } } diff --git a/packages/kbn-config-schema/src/duration/index.ts b/packages/kbn-config-schema/src/duration/index.ts index ff8f96614a193..b96b5a3687bbb 100644 --- a/packages/kbn-config-schema/src/duration/index.ts +++ b/packages/kbn-config-schema/src/duration/index.ts @@ -25,10 +25,14 @@ const timeFormatRegex = /^(0|[1-9][0-9]*)(ms|s|m|h|d|w|M|Y)$/; function stringToDuration(text: string) { const result = timeFormatRegex.exec(text); if (!result) { - throw new Error( - `Failed to parse [${text}] as time value. ` + - `Format must be [ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y')` - ); + const number = Number(text); + if (typeof number !== 'number' || isNaN(number)) { + throw new Error( + `Failed to parse [${text}] as time value. Value must be a duration in milliseconds, or follow the format ` + + `[ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer.` + ); + } + return numberToDuration(number); } const count = parseInt(result[1], 0); @@ -40,8 +44,7 @@ function stringToDuration(text: string) { function numberToDuration(numberMs: number) { if (!Number.isSafeInteger(numberMs) || numberMs < 0) { throw new Error( - `Failed to parse [${numberMs}] as time value. ` + - `Value should be a safe positive integer number.` + `Value in milliseconds is expected to be a safe positive integer, but provided [${numberMs}].` ); } diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index 4d5091eaa09b1..044c3050f9fa8 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -82,7 +82,23 @@ export const internals = Joi.extend([ base: Joi.boolean(), 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 !== 'boolean') { + if (value === undefined) { + return value; + } + + // Allow strings 'true' and 'false' to be coerced to booleans (case-insensitive). + + // From Joi docs on `Joi.boolean`: + // > Generates a schema object that matches a boolean data type. Can also + // > be called via bool(). If the validation convert option is on + // > (enabled by default), a string (either "true" or "false") will be + // converted to a boolean if specified. + if (typeof value === 'string') { + const normalized = value.toLowerCase(); + value = normalized === 'true' ? true : normalized === 'false' ? false : value; + } + + if (typeof value !== 'boolean') { return this.createError('boolean.base', { value }, state, options); } diff --git a/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap index c3f33dc29bf50..0e5f6de2deea8 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap @@ -9,3 +9,7 @@ exports[`returns error when not boolean 1`] = `"expected value of type [boolean] exports[`returns error when not boolean 2`] = `"expected value of type [boolean] but got [Array]"`; exports[`returns error when not boolean 3`] = `"expected value of type [boolean] but got [string]"`; + +exports[`returns error when not boolean 4`] = `"expected value of type [boolean] but got [number]"`; + +exports[`returns error when not boolean 5`] = `"expected value of type [boolean] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap index f6f45a96ca161..ea2102b1776fb 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap @@ -18,6 +18,12 @@ ByteSizeValue { } `; +exports[`#defaultValue can be a string-formatted number 1`] = ` +ByteSizeValue { + "valueInBytes": 1024, +} +`; + exports[`#max returns error when larger 1`] = `"Value is [1mb] ([1048576b]) but it must be equal to or less than [1kb]"`; exports[`#max returns value when smaller 1`] = ` @@ -38,20 +44,18 @@ exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value o exports[`is required by default 1`] = `"expected value of type [ByteSize] but got [undefined]"`; -exports[`returns error when not string or positive safe integer 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-123]"`; +exports[`returns error when not valid string or positive safe integer 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-123]."`; -exports[`returns error when not string or positive safe integer 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]"`; +exports[`returns error when not valid string or positive safe integer 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]."`; -exports[`returns error when not string or positive safe integer 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]"`; +exports[`returns error when not valid string or positive safe integer 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]."`; -exports[`returns error when not string or positive safe integer 4`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]"`; +exports[`returns error when not valid string or positive safe integer 4`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]."`; -exports[`returns error when not string or positive safe integer 5`] = `"expected value of type [ByteSize] but got [Array]"`; +exports[`returns error when not valid string or positive safe integer 5`] = `"expected value of type [ByteSize] but got [Array]"`; -exports[`returns error when not string or positive safe integer 6`] = `"expected value of type [ByteSize] but got [RegExp]"`; +exports[`returns error when not valid string or positive safe integer 6`] = `"expected value of type [ByteSize] but got [RegExp]"`; -exports[`returns value by default 1`] = ` -ByteSizeValue { - "valueInBytes": 123, -} -`; +exports[`returns error when not valid string or positive safe integer 7`] = `"Failed to parse [123foo] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] (e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer."`; + +exports[`returns error when not valid string or positive safe integer 8`] = `"Failed to parse [123 456] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] (e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer."`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap index a21c28e7cc614..c4e4ff652a2d7 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap @@ -6,20 +6,24 @@ exports[`#defaultValue can be a number 1`] = `"PT0.6S"`; exports[`#defaultValue can be a string 1`] = `"PT1H"`; +exports[`#defaultValue can be a string-formatted number 1`] = `"PT0.6S"`; + exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [moment.Duration] but got [undefined]"`; exports[`is required by default 1`] = `"expected value of type [moment.Duration] but got [undefined]"`; -exports[`returns error when not string or non-safe positive integer 1`] = `"Failed to parse [-123] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 1`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [-123]."`; + +exports[`returns error when not valid string or non-safe positive integer 2`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [NaN]."`; -exports[`returns error when not string or non-safe positive integer 2`] = `"Failed to parse [NaN] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 3`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [Infinity]."`; -exports[`returns error when not string or non-safe positive integer 3`] = `"Failed to parse [Infinity] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 4`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [9007199254740992]."`; -exports[`returns error when not string or non-safe positive integer 4`] = `"Failed to parse [9007199254740992] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 5`] = `"expected value of type [moment.Duration] but got [Array]"`; -exports[`returns error when not string or non-safe positive integer 5`] = `"expected value of type [moment.Duration] but got [Array]"`; +exports[`returns error when not valid string or non-safe positive integer 6`] = `"expected value of type [moment.Duration] but got [RegExp]"`; -exports[`returns error when not string or non-safe positive integer 6`] = `"expected value of type [moment.Duration] but got [RegExp]"`; +exports[`returns error when not valid string or non-safe positive integer 7`] = `"Failed to parse [123foo] as time value. Value must be a duration in milliseconds, or follow the format [ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer."`; -exports[`returns value by default 1`] = `"PT2M3S"`; +exports[`returns error when not valid string or non-safe positive integer 8`] = `"Failed to parse [123 456] as time value. Value must be a duration in milliseconds, or follow the format [ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer."`; diff --git a/packages/kbn-config-schema/src/types/boolean_type.test.ts b/packages/kbn-config-schema/src/types/boolean_type.test.ts index d6e274f05e3ff..e94999b505437 100644 --- a/packages/kbn-config-schema/src/types/boolean_type.test.ts +++ b/packages/kbn-config-schema/src/types/boolean_type.test.ts @@ -23,6 +23,17 @@ test('returns value by default', () => { expect(schema.boolean().validate(true)).toBe(true); }); +test('handles boolean strings', () => { + expect(schema.boolean().validate('true')).toBe(true); + expect(schema.boolean().validate('TRUE')).toBe(true); + expect(schema.boolean().validate('True')).toBe(true); + expect(schema.boolean().validate('TrUe')).toBe(true); + expect(schema.boolean().validate('false')).toBe(false); + expect(schema.boolean().validate('FALSE')).toBe(false); + expect(schema.boolean().validate('False')).toBe(false); + expect(schema.boolean().validate('FaLse')).toBe(false); +}); + test('is required by default', () => { expect(() => schema.boolean().validate(undefined)).toThrowErrorMatchingSnapshot(); }); @@ -49,4 +60,8 @@ test('returns error when not boolean', () => { expect(() => schema.boolean().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => schema.boolean().validate('abc')).toThrowErrorMatchingSnapshot(); + + expect(() => schema.boolean().validate(0)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.boolean().validate('no')).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/kbn-config-schema/src/types/byte_size_type.test.ts b/packages/kbn-config-schema/src/types/byte_size_type.test.ts index 67eae1e7c382a..7c65ec2945b49 100644 --- a/packages/kbn-config-schema/src/types/byte_size_type.test.ts +++ b/packages/kbn-config-schema/src/types/byte_size_type.test.ts @@ -23,7 +23,15 @@ import { ByteSizeValue } from '../byte_size_value'; const { byteSize } = schema; test('returns value by default', () => { - expect(byteSize().validate('123b')).toMatchSnapshot(); + expect(byteSize().validate('123b')).toEqual(new ByteSizeValue(123)); +}); + +test('handles numeric strings', () => { + expect(byteSize().validate('123')).toEqual(new ByteSizeValue(123)); +}); + +test('handles numbers', () => { + expect(byteSize().validate(123)).toEqual(new ByteSizeValue(123)); }); test('is required by default', () => { @@ -51,6 +59,14 @@ describe('#defaultValue', () => { ).toMatchSnapshot(); }); + test('can be a string-formatted number', () => { + expect( + byteSize({ + defaultValue: '1024', + }).validate(undefined) + ).toMatchSnapshot(); + }); + test('can be a number', () => { expect( byteSize({ @@ -88,7 +104,7 @@ describe('#max', () => { }); }); -test('returns error when not string or positive safe integer', () => { +test('returns error when not valid string or positive safe integer', () => { expect(() => byteSize().validate(-123)).toThrowErrorMatchingSnapshot(); expect(() => byteSize().validate(NaN)).toThrowErrorMatchingSnapshot(); @@ -100,4 +116,8 @@ test('returns error when not string or positive safe integer', () => { expect(() => byteSize().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => byteSize().validate(/abc/)).toThrowErrorMatchingSnapshot(); + + expect(() => byteSize().validate('123foo')).toThrowErrorMatchingSnapshot(); + + expect(() => byteSize().validate('123 456')).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/kbn-config-schema/src/types/duration_type.test.ts b/packages/kbn-config-schema/src/types/duration_type.test.ts index 39655d43d7b75..09e92ce727f2a 100644 --- a/packages/kbn-config-schema/src/types/duration_type.test.ts +++ b/packages/kbn-config-schema/src/types/duration_type.test.ts @@ -23,7 +23,15 @@ import { schema } from '..'; const { duration, object, contextRef, siblingRef } = schema; test('returns value by default', () => { - expect(duration().validate('123s')).toMatchSnapshot(); + expect(duration().validate('123s')).toEqual(momentDuration(123000)); +}); + +test('handles numeric string', () => { + expect(duration().validate('123000')).toEqual(momentDuration(123000)); +}); + +test('handles number', () => { + expect(duration().validate(123000)).toEqual(momentDuration(123000)); }); test('is required by default', () => { @@ -51,6 +59,14 @@ describe('#defaultValue', () => { ).toMatchSnapshot(); }); + test('can be a string-formatted number', () => { + expect( + duration({ + defaultValue: '600', + }).validate(undefined) + ).toMatchSnapshot(); + }); + test('can be a number', () => { expect( duration({ @@ -124,7 +140,7 @@ Object { }); }); -test('returns error when not string or non-safe positive integer', () => { +test('returns error when not valid string or non-safe positive integer', () => { expect(() => duration().validate(-123)).toThrowErrorMatchingSnapshot(); expect(() => duration().validate(NaN)).toThrowErrorMatchingSnapshot(); @@ -136,4 +152,8 @@ test('returns error when not string or non-safe positive integer', () => { expect(() => duration().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => duration().validate(/abc/)).toThrowErrorMatchingSnapshot(); + + expect(() => duration().validate('123foo')).toThrowErrorMatchingSnapshot(); + + expect(() => duration().validate('123 456')).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/kbn-dev-utils/src/run/README.md b/packages/kbn-dev-utils/src/run/README.md index 913b601058f87..99893a6237668 100644 --- a/packages/kbn-dev-utils/src/run/README.md +++ b/packages/kbn-dev-utils/src/run/README.md @@ -117,7 +117,7 @@ $ node scripts/my_task - *`flags.allowUnexpected: boolean`* - By default, any flag that is passed but not mentioned in `flags.string`, `flags.boolean`, `flags.alias` or `flags.default` will trigger an error, preventing the run function from calling its first argument. If you have a reason to disable this behavior set this option to `true`. + By default, any flag that is passed but not mentioned in `flags.string`, `flags.boolean`, `flags.alias` or `flags.default` will trigger an error, preventing the run function from calling its first argument. If you have a reason to disable this behavior set this option to `true`. Unexpected flags will be collected from argv into `flags.unexpected`. To parse these flags and guess at their types, you can additionally pass `flags.guessTypesForUnexpectedFlags` but that's not recommended. - ***`createFailError(reason: string, options: { exitCode: number, showHelp: boolean }): FailError`*** diff --git a/packages/kbn-dev-utils/src/run/flags.test.ts b/packages/kbn-dev-utils/src/run/flags.test.ts new file mode 100644 index 0000000000000..c730067a84f46 --- /dev/null +++ b/packages/kbn-dev-utils/src/run/flags.test.ts @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getFlags } from './flags'; + +it('gets flags correctly', () => { + expect( + getFlags(['-a', '--abc=bcd', '--foo=bar', '--no-bar', '--foo=baz', '--box', 'yes', '-zxy'], { + flags: { + boolean: ['x'], + string: ['abc'], + alias: { + x: 'extra', + }, + allowUnexpected: true, + }, + }) + ).toMatchInlineSnapshot(` + Object { + "_": Array [], + "abc": "bcd", + "debug": false, + "extra": true, + "help": false, + "quiet": false, + "silent": false, + "unexpected": Array [ + "-a", + "--foo=bar", + "--foo=baz", + "--no-bar", + "--box", + "yes", + "-z", + "-y", + ], + "v": false, + "verbose": false, + "x": true, + } + `); +}); + +it('guesses types for unexpected flags', () => { + expect( + getFlags(['-abc', '--abc=bcd', '--no-foo', '--bar'], { + flags: { + allowUnexpected: true, + guessTypesForUnexpectedFlags: true, + }, + }) + ).toMatchInlineSnapshot(` + Object { + "_": Array [], + "a": true, + "abc": "bcd", + "b": true, + "bar": true, + "c": true, + "debug": false, + "foo": false, + "help": false, + "quiet": false, + "silent": false, + "unexpected": Array [ + "-a", + "-b", + "-c", + "-abc", + "--abc=bcd", + "--no-foo", + "--bar", + ], + "v": false, + "verbose": false, + } + `); +}); diff --git a/packages/kbn-dev-utils/src/run/flags.ts b/packages/kbn-dev-utils/src/run/flags.ts index 6a2966359ece1..c809a40d8512b 100644 --- a/packages/kbn-dev-utils/src/run/flags.ts +++ b/packages/kbn-dev-utils/src/run/flags.ts @@ -37,7 +37,7 @@ export interface Flags { } export function getFlags(argv: string[], options: Options): Flags { - const unexpected: string[] = []; + const unexpectedNames = new Set(); const flagOpts = options.flags || {}; const { verbose, quiet, silent, debug, help, _, ...others } = getopts(argv, { @@ -49,15 +49,64 @@ export function getFlags(argv: string[], options: Options): Flags { }, default: flagOpts.default, unknown: (name: string) => { - unexpected.push(name); + unexpectedNames.add(name); + return flagOpts.guessTypesForUnexpectedFlags; + }, + } as any); + + const unexpected: string[] = []; + for (const unexpectedName of unexpectedNames) { + const matchingArgv: string[] = []; + + iterArgv: for (const [i, v] of argv.entries()) { + for (const prefix of ['--', '-']) { + if (v.startsWith(prefix)) { + // -/--name=value + if (v.startsWith(`${prefix}${unexpectedName}=`)) { + matchingArgv.push(v); + continue iterArgv; + } + + // -/--name (value possibly follows) + if (v === `${prefix}${unexpectedName}`) { + matchingArgv.push(v); - if (options.flags && options.flags.allowUnexpected) { - return true; + // value follows -/--name + if (argv.length > i + 1 && !argv[i + 1].startsWith('-')) { + matchingArgv.push(argv[i + 1]); + } + + continue iterArgv; + } + } } - return false; - }, - } as any); + // special case for `--no-{flag}` disabling of boolean flags + if (v === `--no-${unexpectedName}`) { + matchingArgv.push(v); + continue iterArgv; + } + + // special case for shortcut flags formatted as `-abc` where `a`, `b`, + // and `c` will be three separate unexpected flags + if ( + unexpectedName.length === 1 && + v[0] === '-' && + v[1] !== '-' && + !v.includes('=') && + v.includes(unexpectedName) + ) { + matchingArgv.push(`-${unexpectedName}`); + continue iterArgv; + } + } + + if (matchingArgv.length) { + unexpected.push(...matchingArgv); + } else { + throw new Error(`unable to find unexpected flag named "${unexpectedName}"`); + } + } return { verbose, @@ -75,7 +124,7 @@ export function getHelp(options: Options) { const usage = options.usage || `node ${relative(process.cwd(), process.argv[1])}`; const optionHelp = ( - dedent((options.flags && options.flags.help) || '') + + dedent(options?.flags?.help || '') + '\n' + dedent` --verbose, -v Log verbosely diff --git a/packages/kbn-dev-utils/src/run/run.ts b/packages/kbn-dev-utils/src/run/run.ts index 06746c663b917..1d28d43575729 100644 --- a/packages/kbn-dev-utils/src/run/run.ts +++ b/packages/kbn-dev-utils/src/run/run.ts @@ -36,6 +36,7 @@ export interface Options { description?: string; flags?: { allowUnexpected?: boolean; + guessTypesForUnexpectedFlags?: boolean; help?: string; alias?: { [key: string]: string | string[] }; boolean?: string[]; @@ -46,7 +47,6 @@ export interface Options { export async function run(fn: RunFn, options: Options = {}) { const flags = getFlags(process.argv.slice(2), options); - const allowUnexpected = options.flags ? options.flags.allowUnexpected : false; if (flags.help) { process.stderr.write(getHelp(options)); @@ -97,7 +97,7 @@ export async function run(fn: RunFn, options: Options = {}) { const cleanupTasks: CleanupTask[] = [unhookExit]; try { - if (!allowUnexpected && flags.unexpected.length) { + if (!options.flags?.allowUnexpected && flags.unexpected.length) { throw createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`); } diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 4c519a609d86f..0ec058eeb8a28 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "target", "target": "ES2019", - "declaration": true + "declaration": true, + "declarationMap": true }, "include": [ "src/**/*" diff --git a/packages/kbn-es/src/artifact.js b/packages/kbn-es/src/artifact.js index 19d95e82fe480..9ea78386269d9 100644 --- a/packages/kbn-es/src/artifact.js +++ b/packages/kbn-es/src/artifact.js @@ -27,16 +27,14 @@ const { createHash } = require('crypto'); const path = require('path'); const asyncPipeline = promisify(pipeline); -const V1_VERSIONS_API = 'https://artifacts-api.elastic.co/v1/versions'; +const DAILY_SNAPSHOTS_BASE_URL = 'https://storage.googleapis.com/kibana-ci-es-snapshots-daily'; +const PERMANENT_SNAPSHOTS_BASE_URL = + 'https://storage.googleapis.com/kibana-ci-es-snapshots-permanent'; const { cache } = require('./utils'); const { resolveCustomSnapshotUrl } = require('./custom_snapshots'); const { createCliError, isCliError } = require('./errors'); -const TEST_ES_SNAPSHOT_VERSION = process.env.TEST_ES_SNAPSHOT_VERSION - ? process.env.TEST_ES_SNAPSHOT_VERSION - : 'latest'; - function getChecksumType(checksumUrl) { if (checksumUrl.endsWith('.sha512')) { return 'sha512'; @@ -45,20 +43,6 @@ function getChecksumType(checksumUrl) { throw new Error(`unable to determine checksum type: ${checksumUrl}`); } -function getPlatform(key) { - if (key.includes('-linux-')) { - return 'linux'; - } - - if (key.includes('-windows-')) { - return 'win32'; - } - - if (key.includes('-darwin-')) { - return 'darwin'; - } -} - function headersToString(headers, indent = '') { return [...headers.entries()].reduce( (acc, [key, value]) => `${acc}\n${indent}${key}: ${value}`, @@ -85,6 +69,75 @@ async function retry(log, fn) { return await doAttempt(1); } +// Setting this flag provides an easy way to run the latest un-promoted snapshot without having to look it up +function shouldUseUnverifiedSnapshot() { + return !!process.env.KBN_ES_SNAPSHOT_USE_UNVERIFIED; +} + +async function fetchSnapshotManifest(url, log) { + log.info('Downloading snapshot manifest from %s', chalk.bold(url)); + + const abc = new AbortController(); + const resp = await retry(log, async () => await fetch(url, { signal: abc.signal })); + const json = await resp.text(); + + return { abc, resp, json }; +} + +async function getArtifactSpecForSnapshot(urlVersion, license, log) { + const desiredVersion = urlVersion.replace('-SNAPSHOT', ''); + const desiredLicense = license === 'oss' ? 'oss' : 'default'; + + const customManifestUrl = process.env.ES_SNAPSHOT_MANIFEST; + const primaryManifestUrl = `${DAILY_SNAPSHOTS_BASE_URL}/${desiredVersion}/manifest-latest${ + shouldUseUnverifiedSnapshot() ? '' : '-verified' + }.json`; + const secondaryManifestUrl = `${PERMANENT_SNAPSHOTS_BASE_URL}/${desiredVersion}/manifest.json`; + + let { abc, resp, json } = await fetchSnapshotManifest( + customManifestUrl || primaryManifestUrl, + log + ); + + if (!customManifestUrl && !shouldUseUnverifiedSnapshot() && resp.status === 404) { + log.info('Daily snapshot manifest not found, falling back to permanent manifest'); + ({ abc, resp, json } = await fetchSnapshotManifest(secondaryManifestUrl, log)); + } + + if (resp.status === 404) { + abc.abort(); + throw createCliError(`Snapshots for ${desiredVersion} are not available`); + } + + if (!resp.ok) { + abc.abort(); + throw new Error(`Unable to read snapshot manifest: ${resp.statusText}\n ${json}`); + } + + const manifest = JSON.parse(json); + + const platform = process.platform === 'win32' ? 'windows' : process.platform; + const archive = manifest.archives.find( + archive => + archive.version === desiredVersion && + archive.platform === platform && + archive.license === desiredLicense + ); + + if (!archive) { + throw createCliError( + `Snapshots for ${desiredVersion} are available, but couldn't find an artifact in the manifest for [${desiredVersion}, ${desiredLicense}, ${platform}]` + ); + } + + return { + url: archive.url, + checksumUrl: archive.url + '.sha512', + checksumType: 'sha512', + filename: archive.filename, + }; +} + exports.Artifact = class Artifact { /** * Fetch an Artifact from the Artifact API for a license level and version @@ -100,71 +153,7 @@ exports.Artifact = class Artifact { return new Artifact(customSnapshotArtifactSpec, log); } - const urlBuild = encodeURIComponent(TEST_ES_SNAPSHOT_VERSION); - const url = `${V1_VERSIONS_API}/${urlVersion}/builds/${urlBuild}/projects/elasticsearch`; - - const json = await retry(log, async () => { - log.info('downloading artifact info from %s', chalk.bold(url)); - - const abc = new AbortController(); - const resp = await fetch(url, { signal: abc.signal }); - const json = await resp.text(); - - if (resp.status === 404) { - abc.abort(); - throw createCliError( - `Snapshots for ${version}/${TEST_ES_SNAPSHOT_VERSION} are not available` - ); - } - - if (!resp.ok) { - abc.abort(); - throw new Error(`Unable to read artifact info from ${url}: ${resp.statusText}\n ${json}`); - } - - return json; - }); - - // parse the api response into an array of Artifact objects - const { - project: { packages: artifactInfoMap }, - } = JSON.parse(json); - const filenames = Object.keys(artifactInfoMap); - const hasNoJdkVersions = filenames.some(filename => filename.includes('-no-jdk-')); - const artifactSpecs = filenames.map(filename => ({ - filename, - url: artifactInfoMap[filename].url, - checksumUrl: artifactInfoMap[filename].sha_url, - checksumType: getChecksumType(artifactInfoMap[filename].sha_url), - type: artifactInfoMap[filename].type, - isOss: filename.includes('-oss-'), - platform: getPlatform(filename), - jdkRequired: hasNoJdkVersions ? filename.includes('-no-jdk-') : true, - })); - - // pick the artifact we are going to use for this license/version combo - const reqOss = license === 'oss'; - const reqPlatform = artifactSpecs.some(a => a.platform !== undefined) - ? process.platform - : undefined; - const reqJdkRequired = hasNoJdkVersions ? false : true; - const reqType = process.platform === 'win32' ? 'zip' : 'tar'; - - const artifactSpec = artifactSpecs.find( - spec => - spec.isOss === reqOss && - spec.type === reqType && - spec.platform === reqPlatform && - spec.jdkRequired === reqJdkRequired - ); - - if (!artifactSpec) { - throw new Error( - `Unable to determine artifact for license [${license}] and version [${version}]\n` + - ` options: ${filenames.join(',')}` - ); - } - + const artifactSpec = await getArtifactSpecForSnapshot(urlVersion, license, log); return new Artifact(artifactSpec, log); } diff --git a/packages/kbn-es/src/artifact.test.js b/packages/kbn-es/src/artifact.test.js new file mode 100644 index 0000000000000..985b65c747563 --- /dev/null +++ b/packages/kbn-es/src/artifact.test.js @@ -0,0 +1,191 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ToolingLog } from '@kbn/dev-utils'; +jest.mock('node-fetch'); +import fetch from 'node-fetch'; +const { Response } = jest.requireActual('node-fetch'); + +import { Artifact } from './artifact'; + +const log = new ToolingLog(); +let MOCKS; + +const PLATFORM = process.platform === 'win32' ? 'windows' : process.platform; +const MOCK_VERSION = 'test-version'; +const MOCK_URL = 'http://127.0.0.1:12345'; +const MOCK_FILENAME = 'test-filename'; + +const DAILY_SNAPSHOT_BASE_URL = 'https://storage.googleapis.com/kibana-ci-es-snapshots-daily'; +const PERMANENT_SNAPSHOT_BASE_URL = + 'https://storage.googleapis.com/kibana-ci-es-snapshots-permanent'; + +const createArchive = (params = {}) => { + const license = params.license || 'default'; + + return { + license: 'default', + version: MOCK_VERSION, + url: MOCK_URL + `/${license}`, + platform: PLATFORM, + filename: MOCK_FILENAME + `.${license}`, + ...params, + }; +}; + +const mockFetch = mock => + fetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(mock)))); + +let previousSnapshotManifestValue = null; + +beforeAll(() => { + if ('ES_SNAPSHOT_MANIFEST' in process.env) { + previousSnapshotManifestValue = process.env.ES_SNAPSHOT_MANIFEST; + delete process.env.ES_SNAPSHOT_MANIFEST; + } +}); + +afterAll(() => { + if (previousSnapshotManifestValue !== null) { + process.env.ES_SNAPSHOT_MANIFEST = previousSnapshotManifestValue; + } else { + delete process.env.ES_SNAPSHOT_MANIFEST; + } +}); + +beforeEach(() => { + jest.resetAllMocks(); + + MOCKS = { + valid: { + archives: [createArchive({ license: 'oss' }), createArchive({ license: 'default' })], + }, + }; +}); + +const artifactTest = (requestedLicense, expectedLicense, fetchTimesCalled = 1) => { + return async () => { + const artifact = await Artifact.getSnapshot(requestedLicense, MOCK_VERSION, log); + expect(fetch).toHaveBeenCalledTimes(fetchTimesCalled); + expect(fetch.mock.calls[0][0]).toEqual( + `${DAILY_SNAPSHOT_BASE_URL}/${MOCK_VERSION}/manifest-latest-verified.json` + ); + if (fetchTimesCalled === 2) { + expect(fetch.mock.calls[1][0]).toEqual( + `${PERMANENT_SNAPSHOT_BASE_URL}/${MOCK_VERSION}/manifest.json` + ); + } + expect(artifact.getUrl()).toEqual(MOCK_URL + `/${expectedLicense}`); + expect(artifact.getChecksumUrl()).toEqual(MOCK_URL + `/${expectedLicense}.sha512`); + expect(artifact.getChecksumType()).toEqual('sha512'); + expect(artifact.getFilename()).toEqual(MOCK_FILENAME + `.${expectedLicense}`); + }; +}; + +describe('Artifact', () => { + describe('getSnapshot()', () => { + describe('with default snapshot', () => { + beforeEach(() => { + mockFetch(MOCKS.valid); + }); + + it('should return artifact metadata for a daily oss artifact', artifactTest('oss', 'oss')); + + it( + 'should return artifact metadata for a daily default artifact', + artifactTest('default', 'default') + ); + + it( + 'should default to default license with anything other than "oss"', + artifactTest('INVALID_LICENSE', 'default') + ); + + it('should throw when an artifact cannot be found in the manifest for the specified parameters', async () => { + await expect(Artifact.getSnapshot('default', 'INVALID_VERSION', log)).rejects.toThrow( + "couldn't find an artifact" + ); + }); + }); + + describe('with missing default snapshot', () => { + beforeEach(() => { + fetch.mockReturnValueOnce(Promise.resolve(new Response('', { status: 404 }))); + mockFetch(MOCKS.valid); + }); + + it( + 'should return artifact metadata for a permanent oss artifact', + artifactTest('oss', 'oss', 2) + ); + + it( + 'should return artifact metadata for a permanent default artifact', + artifactTest('default', 'default', 2) + ); + + it( + 'should default to default license with anything other than "oss"', + artifactTest('INVALID_LICENSE', 'default', 2) + ); + + it('should throw when an artifact cannot be found in the manifest for the specified parameters', async () => { + await expect(Artifact.getSnapshot('default', 'INVALID_VERSION', log)).rejects.toThrow( + "couldn't find an artifact" + ); + }); + }); + + describe('with custom snapshot manifest URL', () => { + const CUSTOM_URL = 'http://www.creedthoughts.gov.www/creedthoughts'; + + beforeEach(() => { + process.env.ES_SNAPSHOT_MANIFEST = CUSTOM_URL; + mockFetch(MOCKS.valid); + }); + + it('should use the custom URL when looking for a snapshot', async () => { + await Artifact.getSnapshot('oss', MOCK_VERSION, log); + expect(fetch.mock.calls[0][0]).toEqual(CUSTOM_URL); + }); + + afterEach(() => { + delete process.env.ES_SNAPSHOT_MANIFEST; + }); + }); + + describe('with latest unverified snapshot', () => { + beforeEach(() => { + process.env.KBN_ES_SNAPSHOT_USE_UNVERIFIED = 1; + mockFetch(MOCKS.valid); + }); + + it('should use the daily unverified URL when looking for a snapshot', async () => { + await Artifact.getSnapshot('oss', MOCK_VERSION, log); + expect(fetch.mock.calls[0][0]).toEqual( + `${DAILY_SNAPSHOT_BASE_URL}/${MOCK_VERSION}/manifest-latest.json` + ); + }); + + afterEach(() => { + delete process.env.KBN_ES_SNAPSHOT_USE_UNVERIFIED; + }); + }); + }); +}); diff --git a/packages/kbn-es/src/custom_snapshots.js b/packages/kbn-es/src/custom_snapshots.js index 74de3c2c792fd..c6b00f77f0a88 100644 --- a/packages/kbn-es/src/custom_snapshots.js +++ b/packages/kbn-es/src/custom_snapshots.js @@ -25,8 +25,13 @@ function isVersionFlag(a) { function getCustomSnapshotUrl() { // force use of manually created snapshots until ReindexPutMappings fix - if (!process.env.KBN_ES_SNAPSHOT_URL && !process.argv.some(isVersionFlag)) { - return 'https://storage.googleapis.com/kibana-ci-tmp-artifacts/{name}-{version}-{os}-x86_64.{ext}'; + if ( + !process.env.ES_SNAPSHOT_MANIFEST && + !process.env.KBN_ES_SNAPSHOT_URL && + !process.argv.some(isVersionFlag) + ) { + // return 'https://storage.googleapis.com/kibana-ci-tmp-artifacts/{name}-{version}-{os}-x86_64.{ext}'; + return; } if (process.env.KBN_ES_SNAPSHOT_URL && process.env.KBN_ES_SNAPSHOT_URL !== 'false') { diff --git a/packages/kbn-eslint-plugin-eslint/package.json b/packages/kbn-eslint-plugin-eslint/package.json index badcf13187caf..026938213ac83 100644 --- a/packages/kbn-eslint-plugin-eslint/package.json +++ b/packages/kbn-eslint-plugin-eslint/package.json @@ -4,12 +4,12 @@ "private": true, "license": "Apache-2.0", "peerDependencies": { - "eslint": "6.5.1", + "eslint": "6.8.0", "babel-eslint": "^10.0.3" }, "dependencies": { "micromatch": "3.1.10", "dedent": "^0.7.0", - "eslint-module-utils": "2.4.1" + "eslint-module-utils": "2.5.0" } } diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 7c5937af441a2..c3f3f2f477fdd 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -37054,8 +37054,8 @@ const tooling_log_1 = __webpack_require__(415); const fail_1 = __webpack_require__(425); const flags_1 = __webpack_require__(426); async function run(fn, options = {}) { + var _a; const flags = flags_1.getFlags(process.argv.slice(2), options); - const allowUnexpected = options.flags ? options.flags.allowUnexpected : false; if (flags.help) { process.stderr.write(flags_1.getHelp(options)); process.exit(1); @@ -37098,7 +37098,7 @@ async function run(fn, options = {}) { const unhookExit = exit_hook_1.default(doCleanup); const cleanupTasks = [unhookExit]; try { - if (!allowUnexpected && flags.unexpected.length) { + if (!((_a = options.flags) === null || _a === void 0 ? void 0 : _a.allowUnexpected) && flags.unexpected.length) { throw fail_1.createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`); } try { @@ -37218,7 +37218,7 @@ const path_1 = __webpack_require__(16); const dedent_1 = tslib_1.__importDefault(__webpack_require__(14)); const getopts_1 = tslib_1.__importDefault(__webpack_require__(427)); function getFlags(argv, options) { - const unexpected = []; + const unexpectedNames = new Set(); const flagOpts = options.flags || {}; const { verbose, quiet, silent, debug, help, _, ...others } = getopts_1.default(argv, { string: flagOpts.string, @@ -37229,13 +37229,55 @@ function getFlags(argv, options) { }, default: flagOpts.default, unknown: (name) => { - unexpected.push(name); - if (options.flags && options.flags.allowUnexpected) { - return true; - } - return false; + unexpectedNames.add(name); + return flagOpts.guessTypesForUnexpectedFlags; }, }); + const unexpected = []; + for (const unexpectedName of unexpectedNames) { + const matchingArgv = []; + iterArgv: for (const [i, v] of argv.entries()) { + for (const prefix of ['--', '-']) { + if (v.startsWith(prefix)) { + // -/--name=value + if (v.startsWith(`${prefix}${unexpectedName}=`)) { + matchingArgv.push(v); + continue iterArgv; + } + // -/--name (value possibly follows) + if (v === `${prefix}${unexpectedName}`) { + matchingArgv.push(v); + // value follows -/--name + if (argv.length > i + 1 && !argv[i + 1].startsWith('-')) { + matchingArgv.push(argv[i + 1]); + } + continue iterArgv; + } + } + } + // special case for `--no-{flag}` disabling of boolean flags + if (v === `--no-${unexpectedName}`) { + matchingArgv.push(v); + continue iterArgv; + } + // special case for shortcut flags formatted as `-abc` where `a`, `b`, + // and `c` will be three separate unexpected flags + if (unexpectedName.length === 1 && + v[0] === '-' && + v[1] !== '-' && + !v.includes('=') && + v.includes(unexpectedName)) { + matchingArgv.push(`-${unexpectedName}`); + continue iterArgv; + } + } + if (matchingArgv.length) { + unexpected.push(...matchingArgv); + } + else { + throw new Error(`unable to find unexpected flag named "${unexpectedName}"`); + } + } return { verbose, quiet, @@ -37249,8 +37291,9 @@ function getFlags(argv, options) { } exports.getFlags = getFlags; function getHelp(options) { + var _a, _b; const usage = options.usage || `node ${path_1.relative(process.cwd(), process.argv[1])}`; - const optionHelp = (dedent_1.default((options.flags && options.flags.help) || '') + + const optionHelp = (dedent_1.default(((_b = (_a = options) === null || _a === void 0 ? void 0 : _a.flags) === null || _b === void 0 ? void 0 : _b.help) || '') + '\n' + dedent_1.default ` --verbose, -v Log verbosely @@ -79815,7 +79858,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(704); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(909); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(914); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -79997,8 +80040,8 @@ const EventEmitter = __webpack_require__(379); const path = __webpack_require__(16); const arrify = __webpack_require__(706); const globby = __webpack_require__(707); -const cpFile = __webpack_require__(900); -const CpyError = __webpack_require__(907); +const cpFile = __webpack_require__(905); +const CpyError = __webpack_require__(912); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -80127,8 +80170,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(708); const glob = __webpack_require__(502); const fastGlob = __webpack_require__(710); -const dirGlob = __webpack_require__(893); -const gitignore = __webpack_require__(896); +const dirGlob = __webpack_require__(898); +const gitignore = __webpack_require__(901); const DEFAULT_FILTER = () => false; @@ -80379,11 +80422,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(712); var taskManager = __webpack_require__(713); -var reader_async_1 = __webpack_require__(864); -var reader_stream_1 = __webpack_require__(888); -var reader_sync_1 = __webpack_require__(889); -var arrayUtils = __webpack_require__(891); -var streamUtils = __webpack_require__(892); +var reader_async_1 = __webpack_require__(869); +var reader_stream_1 = __webpack_require__(893); +var reader_sync_1 = __webpack_require__(894); +var arrayUtils = __webpack_require__(896); +var streamUtils = __webpack_require__(897); /** * Synchronous API. */ @@ -81023,9 +81066,9 @@ var extend = __webpack_require__(830); */ var compilers = __webpack_require__(833); -var parsers = __webpack_require__(860); -var cache = __webpack_require__(861); -var utils = __webpack_require__(862); +var parsers = __webpack_require__(865); +var cache = __webpack_require__(866); +var utils = __webpack_require__(867); var MAX_LENGTH = 1024 * 64; /** @@ -99558,9 +99601,9 @@ var toRegex = __webpack_require__(721); */ var compilers = __webpack_require__(850); -var parsers = __webpack_require__(856); -var Extglob = __webpack_require__(859); -var utils = __webpack_require__(858); +var parsers = __webpack_require__(861); +var Extglob = __webpack_require__(864); +var utils = __webpack_require__(863); var MAX_LENGTH = 1024 * 64; /** @@ -100070,7 +100113,7 @@ var parsers = __webpack_require__(854); * Module dependencies */ -var debug = __webpack_require__(793)('expand-brackets'); +var debug = __webpack_require__(856)('expand-brackets'); var extend = __webpack_require__(730); var Snapdragon = __webpack_require__(760); var toRegex = __webpack_require__(721); @@ -100664,12 +100707,839 @@ exports.createRegex = function(pattern, include) { /* 856 */ /***/ (function(module, exports, __webpack_require__) { +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ + +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(857); +} else { + module.exports = __webpack_require__(860); +} + + +/***/ }), +/* 857 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(858); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } + + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return; + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + + +/***/ }), +/* 858 */ +/***/ (function(module, exports, __webpack_require__) { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(859); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + +exports.formatters = {}; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ + +function selectColor(namespace) { + var hash = 0, i; + + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return exports.colors[Math.abs(hash) % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function createDebug(namespace) { + + function debug() { + // disabled? + if (!debug.enabled) return; + + var self = debug; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); + + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + return debug; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + exports.names = []; + exports.skips = []; + + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + + +/***/ }), +/* 859 */ +/***/ (function(module, exports) { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + + +/***/ }), +/* 860 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var tty = __webpack_require__(480); +var util = __webpack_require__(29); + +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(858); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(function (key) { + return /^debug_/i.test(key); +}).reduce(function (obj, key) { + // camel-case + var prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + + // coerce string value into JS value + var val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) val = true; + else if (/^(no|off|false|disabled)$/i.test(val)) val = false; + else if (val === 'null') val = null; + else val = Number(val); + + obj[prop] = val; + return obj; +}, {}); + +/** + * The file descriptor to write the `debug()` calls to. + * Set the `DEBUG_FD` env variable to override with another value. i.e.: + * + * $ DEBUG_FD=3 node script.js 3>debug.log + */ + +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + +if (1 !== fd && 2 !== fd) { + util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() +} + +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +exports.formatters.o = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n').map(function(str) { + return str.trim() + }).join(' '); +}; + +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ + +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; + + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = new Date().toUTCString() + + ' ' + name + ' ' + args[0]; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ + +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + if (null == namespaces) { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } else { + process.env.DEBUG = namespaces; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Copied from `node/src/node.js`. + * + * XXX: It's lame that node doesn't expose this API out-of-the-box. It also + * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. + */ + +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); + + // Note stream._type is used for test-module-load-list.js + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + + // Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + case 'FILE': + var fs = __webpack_require__(23); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + var net = __webpack_require__(798); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); + + // FIXME Should probably have an option in net.Socket to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stream.readable = false; + stream.read = null; + stream._type = 'pipe'; + + // FIXME Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init (debug) { + debug.inspectOpts = {}; + + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ + +exports.enable(load()); + + +/***/ }), +/* 861 */ +/***/ (function(module, exports, __webpack_require__) { + "use strict"; var brackets = __webpack_require__(851); -var define = __webpack_require__(857); -var utils = __webpack_require__(858); +var define = __webpack_require__(862); +var utils = __webpack_require__(863); /** * Characters to use in text regex (we want to "not" match @@ -100824,7 +101694,7 @@ module.exports = parsers; /***/ }), -/* 857 */ +/* 862 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100862,7 +101732,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 858 */ +/* 863 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100938,7 +101808,7 @@ utils.createRegex = function(str) { /***/ }), -/* 859 */ +/* 864 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100949,7 +101819,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(760); -var define = __webpack_require__(857); +var define = __webpack_require__(862); var extend = __webpack_require__(730); /** @@ -100957,7 +101827,7 @@ var extend = __webpack_require__(730); */ var compilers = __webpack_require__(850); -var parsers = __webpack_require__(856); +var parsers = __webpack_require__(861); /** * Customize Snapdragon parser and renderer @@ -101023,7 +101893,7 @@ module.exports = Extglob; /***/ }), -/* 860 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101113,14 +101983,14 @@ function textRegex(pattern) { /***/ }), -/* 861 */ +/* 866 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(842))(); /***/ }), -/* 862 */ +/* 867 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101138,7 +102008,7 @@ utils.define = __webpack_require__(829); utils.diff = __webpack_require__(846); utils.extend = __webpack_require__(830); utils.pick = __webpack_require__(847); -utils.typeOf = __webpack_require__(863); +utils.typeOf = __webpack_require__(868); utils.unique = __webpack_require__(733); /** @@ -101436,7 +102306,7 @@ utils.unixify = function(options) { /***/ }), -/* 863 */ +/* 868 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -101571,7 +102441,7 @@ function isBuffer(val) { /***/ }), -/* 864 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101590,9 +102460,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(865); -var reader_1 = __webpack_require__(878); -var fs_stream_1 = __webpack_require__(882); +var readdir = __webpack_require__(870); +var reader_1 = __webpack_require__(883); +var fs_stream_1 = __webpack_require__(887); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -101653,15 +102523,15 @@ exports.default = ReaderAsync; /***/ }), -/* 865 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(866); -const readdirAsync = __webpack_require__(874); -const readdirStream = __webpack_require__(877); +const readdirSync = __webpack_require__(871); +const readdirAsync = __webpack_require__(879); +const readdirStream = __webpack_require__(882); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -101745,7 +102615,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 866 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101753,11 +102623,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(867); +const DirectoryReader = __webpack_require__(872); let syncFacade = { - fs: __webpack_require__(872), - forEach: __webpack_require__(873), + fs: __webpack_require__(877), + forEach: __webpack_require__(878), sync: true }; @@ -101786,7 +102656,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 867 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101795,9 +102665,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(868); -const stat = __webpack_require__(870); -const call = __webpack_require__(871); +const normalizeOptions = __webpack_require__(873); +const stat = __webpack_require__(875); +const call = __webpack_require__(876); /** * Asynchronously reads the contents of a directory and streams the results @@ -102173,14 +103043,14 @@ module.exports = DirectoryReader; /***/ }), -/* 868 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(869); +const globToRegExp = __webpack_require__(874); module.exports = normalizeOptions; @@ -102357,7 +103227,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 869 */ +/* 874 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -102494,13 +103364,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 870 */ +/* 875 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(871); +const call = __webpack_require__(876); module.exports = stat; @@ -102575,7 +103445,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 871 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102636,14 +103506,14 @@ function callOnce (fn) { /***/ }), -/* 872 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(871); +const call = __webpack_require__(876); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -102707,7 +103577,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 873 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102736,7 +103606,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 874 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102744,12 +103614,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(875); -const DirectoryReader = __webpack_require__(867); +const maybe = __webpack_require__(880); +const DirectoryReader = __webpack_require__(872); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(876), + forEach: __webpack_require__(881), async: true }; @@ -102791,7 +103661,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 875 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102818,7 +103688,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 876 */ +/* 881 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102854,7 +103724,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 877 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102862,11 +103732,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(867); +const DirectoryReader = __webpack_require__(872); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(876), + forEach: __webpack_require__(881), async: true }; @@ -102886,16 +103756,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 878 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(879); -var entry_1 = __webpack_require__(881); -var pathUtil = __webpack_require__(880); +var deep_1 = __webpack_require__(884); +var entry_1 = __webpack_require__(886); +var pathUtil = __webpack_require__(885); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -102961,13 +103831,13 @@ exports.default = Reader; /***/ }), -/* 879 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(880); +var pathUtils = __webpack_require__(885); var patternUtils = __webpack_require__(714); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -103051,7 +103921,7 @@ exports.default = DeepFilter; /***/ }), -/* 880 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103082,13 +103952,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 881 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(880); +var pathUtils = __webpack_require__(885); var patternUtils = __webpack_require__(714); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -103174,7 +104044,7 @@ exports.default = EntryFilter; /***/ }), -/* 882 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103194,8 +104064,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(883); -var fs_1 = __webpack_require__(887); +var fsStat = __webpack_require__(888); +var fs_1 = __webpack_require__(892); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -103245,14 +104115,14 @@ exports.default = FileSystemStream; /***/ }), -/* 883 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(884); -const statProvider = __webpack_require__(886); +const optionsManager = __webpack_require__(889); +const statProvider = __webpack_require__(891); /** * Asynchronous API. */ @@ -103283,13 +104153,13 @@ exports.statSync = statSync; /***/ }), -/* 884 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(885); +const fsAdapter = __webpack_require__(890); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -103302,7 +104172,7 @@ exports.prepare = prepare; /***/ }), -/* 885 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103325,7 +104195,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 886 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103377,7 +104247,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 887 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103408,7 +104278,7 @@ exports.default = FileSystem; /***/ }), -/* 888 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103428,9 +104298,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(865); -var reader_1 = __webpack_require__(878); -var fs_stream_1 = __webpack_require__(882); +var readdir = __webpack_require__(870); +var reader_1 = __webpack_require__(883); +var fs_stream_1 = __webpack_require__(887); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -103498,7 +104368,7 @@ exports.default = ReaderStream; /***/ }), -/* 889 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103517,9 +104387,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(865); -var reader_1 = __webpack_require__(878); -var fs_sync_1 = __webpack_require__(890); +var readdir = __webpack_require__(870); +var reader_1 = __webpack_require__(883); +var fs_sync_1 = __webpack_require__(895); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -103579,7 +104449,7 @@ exports.default = ReaderSync; /***/ }), -/* 890 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103598,8 +104468,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(883); -var fs_1 = __webpack_require__(887); +var fsStat = __webpack_require__(888); +var fs_1 = __webpack_require__(892); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -103645,7 +104515,7 @@ exports.default = FileSystemSync; /***/ }), -/* 891 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103661,7 +104531,7 @@ exports.flatten = flatten; /***/ }), -/* 892 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103682,13 +104552,13 @@ exports.merge = merge; /***/ }), -/* 893 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(894); +const pathType = __webpack_require__(899); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -103754,13 +104624,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 894 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(895); +const pify = __webpack_require__(900); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -103803,7 +104673,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 895 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103894,7 +104764,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 896 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103902,9 +104772,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(710); -const gitIgnore = __webpack_require__(897); -const pify = __webpack_require__(898); -const slash = __webpack_require__(899); +const gitIgnore = __webpack_require__(902); +const pify = __webpack_require__(903); +const slash = __webpack_require__(904); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -104002,7 +104872,7 @@ module.exports.sync = options => { /***/ }), -/* 897 */ +/* 902 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -104471,7 +105341,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 898 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104546,7 +105416,7 @@ module.exports = (input, options) => { /***/ }), -/* 899 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104564,17 +105434,17 @@ module.exports = input => { /***/ }), -/* 900 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(901); -const CpFileError = __webpack_require__(902); -const fs = __webpack_require__(904); -const ProgressEmitter = __webpack_require__(906); +const {Buffer} = __webpack_require__(906); +const CpFileError = __webpack_require__(907); +const fs = __webpack_require__(909); +const ProgressEmitter = __webpack_require__(911); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -104728,7 +105598,7 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 901 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ @@ -104796,12 +105666,12 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 902 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(903); +const NestedError = __webpack_require__(908); class CpFileError extends NestedError { constructor(message, nested) { @@ -104815,7 +105685,7 @@ module.exports = CpFileError; /***/ }), -/* 903 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(509); @@ -104869,15 +105739,15 @@ module.exports = NestedError; /***/ }), -/* 904 */ +/* 909 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(559); -const pify = __webpack_require__(905); -const CpFileError = __webpack_require__(902); +const pify = __webpack_require__(910); +const CpFileError = __webpack_require__(907); const fsP = pify(fs); @@ -105022,7 +105892,7 @@ if (fs.copyFileSync) { /***/ }), -/* 905 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105097,7 +105967,7 @@ module.exports = (input, options) => { /***/ }), -/* 906 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105138,12 +106008,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 907 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(908); +const NestedError = __webpack_require__(913); class CpyError extends NestedError { constructor(message, nested) { @@ -105157,7 +106027,7 @@ module.exports = CpyError; /***/ }), -/* 908 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -105213,7 +106083,7 @@ module.exports = NestedError; /***/ }), -/* 909 */ +/* 914 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/renovate.json5 b/renovate.json5 index ecc9b3b2ceb62..a5983283a9e85 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -46,35 +46,6 @@ enabled: false, }, packageRules: [ - { - groupSlug: 'eslint', - groupName: 'eslint related packages', - packagePatterns: [ - '(\\b|_)eslint(\\b|_)', - ], - }, - { - groupSlug: 'babel', - groupName: 'babel related packages', - packagePatterns: [ - '(\\b|_)babel(\\b|_)', - ], - packageNames: [ - 'core-js', - '@types/core-js', - '@babel/preset-react', - '@types/babel__preset-react', - '@babel/preset-typescript', - '@types/babel__preset-typescript', - ], - }, - { - groupSlug: 'jest', - groupName: 'jest related packages', - packagePatterns: [ - '(\\b|_)jest(\\b|_)', - ], - }, { groupSlug: '@elastic/charts', groupName: '@elastic/charts related packages', @@ -88,31 +59,11 @@ masterIssueApproval: false, }, { - groupSlug: 'mocha', - groupName: 'mocha related packages', - packagePatterns: [ - '(\\b|_)mocha(\\b|_)', - ], - }, - { - groupSlug: 'karma', - groupName: 'karma related packages', - packagePatterns: [ - '(\\b|_)karma(\\b|_)', - ], - }, - { - groupSlug: 'gulp', - groupName: 'gulp related packages', - packagePatterns: [ - '(\\b|_)gulp(\\b|_)', - ], - }, - { - groupSlug: 'grunt', - groupName: 'grunt related packages', - packagePatterns: [ - '(\\b|_)grunt(\\b|_)', + groupSlug: '@reach/router', + groupName: '@reach/router related packages', + packageNames: [ + '@reach/router', + '@types/reach__router', ], }, { @@ -122,126 +73,6 @@ '(\\b|_)angular(\\b|_)', ], }, - { - groupSlug: 'd3', - groupName: 'd3 related packages', - packagePatterns: [ - '(\\b|_)d3(\\b|_)', - ], - }, - { - groupSlug: 'react', - groupName: 'react related packages', - packagePatterns: [ - '(\\b|_)react(\\b|_)', - '(\\b|_)redux(\\b|_)', - '(\\b|_)enzyme(\\b|_)', - ], - packageNames: [ - 'ngreact', - '@types/ngreact', - 'recompose', - '@types/recompose', - 'prop-types', - '@types/prop-types', - 'typescript-fsa-reducers', - '@types/typescript-fsa-reducers', - 'reselect', - '@types/reselect', - ], - }, - { - groupSlug: 'moment', - groupName: 'moment related packages', - packagePatterns: [ - '(\\b|_)moment(\\b|_)', - ], - }, - { - groupSlug: 'graphql', - groupName: 'graphql related packages', - packagePatterns: [ - '(\\b|_)graphql(\\b|_)', - '(\\b|_)apollo(\\b|_)', - ], - }, - { - groupSlug: 'webpack', - groupName: 'webpack related packages', - packagePatterns: [ - '(\\b|_)webpack(\\b|_)', - '(\\b|_)loader(\\b|_)', - '(\\b|_)acorn(\\b|_)', - '(\\b|_)terser(\\b|_)', - ], - packageNames: [ - 'mini-css-extract-plugin', - '@types/mini-css-extract-plugin', - 'chokidar', - '@types/chokidar', - ], - }, - { - groupSlug: 'vega', - groupName: 'vega related packages', - packagePatterns: [ - '(\\b|_)vega(\\b|_)', - ], - enabled: false, - }, - { - groupSlug: 'language server', - groupName: 'language server related packages', - packageNames: [ - 'vscode-jsonrpc', - '@types/vscode-jsonrpc', - 'vscode-languageserver', - '@types/vscode-languageserver', - 'vscode-languageserver-types', - '@types/vscode-languageserver-types', - ], - }, - { - groupSlug: 'hapi', - groupName: 'hapi related packages', - packagePatterns: [ - '(\\b|_)hapi(\\b|_)', - ], - packageNames: [ - 'hapi', - '@types/hapi', - 'joi', - '@types/joi', - 'boom', - '@types/boom', - 'hoek', - '@types/hoek', - 'h2o2', - '@types/h2o2', - '@elastic/good', - '@types/elastic__good', - 'good-squeeze', - '@types/good-squeeze', - 'inert', - '@types/inert', - ], - }, - { - groupSlug: 'dragselect', - groupName: 'dragselect related packages', - packageNames: [ - 'dragselect', - '@types/dragselect', - ], - labels: [ - 'release_note:skip', - 'Team:Operations', - 'renovate', - 'v8.0.0', - 'v7.6.0', - ':ml', - ], - }, { groupSlug: 'api-documenter', groupName: 'api-documenter related packages', @@ -254,47 +85,34 @@ enabled: false, }, { - groupSlug: 'jsts', - groupName: 'jsts related packages', + groupSlug: 'archiver', + groupName: 'archiver related packages', packageNames: [ - 'jsts', - '@types/jsts', - ], - allowedVersions: '^1.6.2', - }, - { - groupSlug: 'storybook', - groupName: 'storybook related packages', - packagePatterns: [ - '(\\b|_)storybook(\\b|_)', + 'archiver', + '@types/archiver', ], }, { - groupSlug: 'typescript', - groupName: 'typescript related packages', + groupSlug: 'babel', + groupName: 'babel related packages', packagePatterns: [ - '(\\b|_)ts(\\b|_)', - '(\\b|_)typescript(\\b|_)', + '(\\b|_)babel(\\b|_)', ], packageNames: [ - 'tslib', - '@types/tslib', - ], - }, - { - groupSlug: 'json-stable-stringify', - groupName: 'json-stable-stringify related packages', - packageNames: [ - 'json-stable-stringify', - '@types/json-stable-stringify', + 'core-js', + '@types/core-js', + '@babel/preset-react', + '@types/babel__preset-react', + '@babel/preset-typescript', + '@types/babel__preset-typescript', ], }, { - groupSlug: 'lodash.clonedeep', - groupName: 'lodash.clonedeep related packages', + groupSlug: 'base64-js', + groupName: 'base64-js related packages', packageNames: [ - 'lodash.clonedeep', - '@types/lodash.clonedeep', + 'base64-js', + '@types/base64-js', ], }, { @@ -321,6 +139,14 @@ '@types/cheerio', ], }, + { + groupSlug: 'chroma-js', + groupName: 'chroma-js related packages', + packageNames: [ + 'chroma-js', + '@types/chroma-js', + ], + }, { groupSlug: 'chromedriver', groupName: 'chromedriver related packages', @@ -337,6 +163,45 @@ '@types/classnames', ], }, + { + groupSlug: 'cmd-shim', + groupName: 'cmd-shim related packages', + packageNames: [ + 'cmd-shim', + '@types/cmd-shim', + ], + }, + { + groupSlug: 'color', + groupName: 'color related packages', + packageNames: [ + 'color', + '@types/color', + ], + }, + { + groupSlug: 'cpy', + groupName: 'cpy related packages', + packageNames: [ + 'cpy', + '@types/cpy', + ], + }, + { + groupSlug: 'cytoscape', + groupName: 'cytoscape related packages', + packageNames: [ + 'cytoscape', + '@types/cytoscape', + ], + }, + { + groupSlug: 'd3', + groupName: 'd3 related packages', + packagePatterns: [ + '(\\b|_)d3(\\b|_)', + ], + }, { groupSlug: 'dedent', groupName: 'dedent related packages', @@ -353,6 +218,22 @@ '@types/delete-empty', ], }, + { + groupSlug: 'dragselect', + groupName: 'dragselect related packages', + packageNames: [ + 'dragselect', + '@types/dragselect', + ], + labels: [ + 'release_note:skip', + 'Team:Operations', + 'renovate', + 'v8.0.0', + 'v7.6.0', + ':ml', + ], + }, { groupSlug: 'elasticsearch', groupName: 'elasticsearch related packages', @@ -361,6 +242,21 @@ '@types/elasticsearch', ], }, + { + groupSlug: 'eslint', + groupName: 'eslint related packages', + packagePatterns: [ + '(\\b|_)eslint(\\b|_)', + ], + }, + { + groupSlug: 'fancy-log', + groupName: 'fancy-log related packages', + packageNames: [ + 'fancy-log', + '@types/fancy-log', + ], + }, { groupSlug: 'fetch-mock', groupName: 'fetch-mock related packages', @@ -369,6 +265,14 @@ '@types/fetch-mock', ], }, + { + groupSlug: 'file-saver', + groupName: 'file-saver related packages', + packageNames: [ + 'file-saver', + '@types/file-saver', + ], + }, { groupSlug: 'getopts', groupName: 'getopts related packages', @@ -377,6 +281,22 @@ '@types/getopts', ], }, + { + groupSlug: 'getos', + groupName: 'getos related packages', + packageNames: [ + 'getos', + '@types/getos', + ], + }, + { + groupSlug: 'git-url-parse', + groupName: 'git-url-parse related packages', + packageNames: [ + 'git-url-parse', + '@types/git-url-parse', + ], + }, { groupSlug: 'glob', groupName: 'glob related packages', @@ -394,387 +314,387 @@ ], }, { - groupSlug: 'has-ansi', - groupName: 'has-ansi related packages', - packageNames: [ - 'has-ansi', - '@types/has-ansi', - ], - }, - { - groupSlug: 'history', - groupName: 'history related packages', - packageNames: [ - 'history', - '@types/history', + groupSlug: 'graphql', + groupName: 'graphql related packages', + packagePatterns: [ + '(\\b|_)graphql(\\b|_)', + '(\\b|_)apollo(\\b|_)', ], }, { - groupSlug: 'jquery', - groupName: 'jquery related packages', - packageNames: [ - 'jquery', - '@types/jquery', + groupSlug: 'grunt', + groupName: 'grunt related packages', + packagePatterns: [ + '(\\b|_)grunt(\\b|_)', ], }, { - groupSlug: 'js-yaml', - groupName: 'js-yaml related packages', - packageNames: [ - 'js-yaml', - '@types/js-yaml', + groupSlug: 'gulp', + groupName: 'gulp related packages', + packagePatterns: [ + '(\\b|_)gulp(\\b|_)', ], }, { - groupSlug: 'json5', - groupName: 'json5 related packages', - packageNames: [ - 'json5', - '@types/json5', + groupSlug: 'hapi', + groupName: 'hapi related packages', + packagePatterns: [ + '(\\b|_)hapi(\\b|_)', ], - }, - { - groupSlug: 'license-checker', - groupName: 'license-checker related packages', packageNames: [ - 'license-checker', - '@types/license-checker', + 'hapi', + '@types/hapi', + 'joi', + '@types/joi', + 'boom', + '@types/boom', + 'hoek', + '@types/hoek', + 'h2o2', + '@types/h2o2', + '@elastic/good', + '@types/elastic__good', + 'good-squeeze', + '@types/good-squeeze', + 'inert', + '@types/inert', ], }, { - groupSlug: 'listr', - groupName: 'listr related packages', + groupSlug: 'has-ansi', + groupName: 'has-ansi related packages', packageNames: [ - 'listr', - '@types/listr', + 'has-ansi', + '@types/has-ansi', ], }, { - groupSlug: 'lodash', - groupName: 'lodash related packages', + groupSlug: 'history', + groupName: 'history related packages', packageNames: [ - 'lodash', - '@types/lodash', + 'history', + '@types/history', ], }, { - groupSlug: 'lru-cache', - groupName: 'lru-cache related packages', + groupSlug: 'indent-string', + groupName: 'indent-string related packages', packageNames: [ - 'lru-cache', - '@types/lru-cache', + 'indent-string', + '@types/indent-string', ], }, { - groupSlug: 'markdown-it', - groupName: 'markdown-it related packages', + groupSlug: 'intl-relativeformat', + groupName: 'intl-relativeformat related packages', packageNames: [ - 'markdown-it', - '@types/markdown-it', + 'intl-relativeformat', + '@types/intl-relativeformat', ], }, { - groupSlug: 'minimatch', - groupName: 'minimatch related packages', - packageNames: [ - 'minimatch', - '@types/minimatch', + groupSlug: 'jest', + groupName: 'jest related packages', + packagePatterns: [ + '(\\b|_)jest(\\b|_)', ], }, { - groupSlug: 'mustache', - groupName: 'mustache related packages', + groupSlug: 'jquery', + groupName: 'jquery related packages', packageNames: [ - 'mustache', - '@types/mustache', + 'jquery', + '@types/jquery', ], }, { - groupSlug: 'node', - groupName: 'node related packages', + groupSlug: 'js-yaml', + groupName: 'js-yaml related packages', packageNames: [ - 'node', - '@types/node', + 'js-yaml', + '@types/js-yaml', ], }, { - groupSlug: 'opn', - groupName: 'opn related packages', + groupSlug: 'jsdom', + groupName: 'jsdom related packages', packageNames: [ - 'opn', - '@types/opn', + 'jsdom', + '@types/jsdom', ], }, { - groupSlug: 'pngjs', - groupName: 'pngjs related packages', + groupSlug: 'json-stable-stringify', + groupName: 'json-stable-stringify related packages', packageNames: [ - 'pngjs', - '@types/pngjs', + 'json-stable-stringify', + '@types/json-stable-stringify', ], }, { - groupSlug: 'podium', - groupName: 'podium related packages', + groupSlug: 'json5', + groupName: 'json5 related packages', packageNames: [ - 'podium', - '@types/podium', + 'json5', + '@types/json5', ], }, { - groupSlug: '@reach/router', - groupName: '@reach/router related packages', + groupSlug: 'jsonwebtoken', + groupName: 'jsonwebtoken related packages', packageNames: [ - '@reach/router', - '@types/reach__router', + 'jsonwebtoken', + '@types/jsonwebtoken', ], }, { - groupSlug: 'request', - groupName: 'request related packages', + groupSlug: 'jsts', + groupName: 'jsts related packages', packageNames: [ - 'request', - '@types/request', + 'jsts', + '@types/jsts', ], + allowedVersions: '^1.6.2', }, { - groupSlug: 'selenium-webdriver', - groupName: 'selenium-webdriver related packages', - packageNames: [ - 'selenium-webdriver', - '@types/selenium-webdriver', + groupSlug: 'karma', + groupName: 'karma related packages', + packagePatterns: [ + '(\\b|_)karma(\\b|_)', ], }, { - groupSlug: 'semver', - groupName: 'semver related packages', + groupSlug: 'language server', + groupName: 'language server related packages', packageNames: [ - 'semver', - '@types/semver', + 'vscode-jsonrpc', + '@types/vscode-jsonrpc', + 'vscode-languageserver', + '@types/vscode-languageserver', + 'vscode-languageserver-types', + '@types/vscode-languageserver-types', ], }, { - groupSlug: 'sinon', - groupName: 'sinon related packages', + groupSlug: 'license-checker', + groupName: 'license-checker related packages', packageNames: [ - 'sinon', - '@types/sinon', + 'license-checker', + '@types/license-checker', ], }, { - groupSlug: 'strip-ansi', - groupName: 'strip-ansi related packages', + groupSlug: 'listr', + groupName: 'listr related packages', packageNames: [ - 'strip-ansi', - '@types/strip-ansi', + 'listr', + '@types/listr', ], }, { - groupSlug: 'styled-components', - groupName: 'styled-components related packages', + groupSlug: 'lodash', + groupName: 'lodash related packages', packageNames: [ - 'styled-components', - '@types/styled-components', + 'lodash', + '@types/lodash', ], }, { - groupSlug: 'supertest', - groupName: 'supertest related packages', + groupSlug: 'lodash.clonedeep', + groupName: 'lodash.clonedeep related packages', packageNames: [ - 'supertest', - '@types/supertest', + 'lodash.clonedeep', + '@types/lodash.clonedeep', ], }, { - groupSlug: 'supertest-as-promised', - groupName: 'supertest-as-promised related packages', + groupSlug: 'lodash.clonedeepwith', + groupName: 'lodash.clonedeepwith related packages', packageNames: [ - 'supertest-as-promised', - '@types/supertest-as-promised', + 'lodash.clonedeepwith', + '@types/lodash.clonedeepwith', ], }, { - groupSlug: 'type-detect', - groupName: 'type-detect related packages', + groupSlug: 'log-symbols', + groupName: 'log-symbols related packages', packageNames: [ - 'type-detect', - '@types/type-detect', + 'log-symbols', + '@types/log-symbols', ], }, { - groupSlug: 'uuid', - groupName: 'uuid related packages', + groupSlug: 'lru-cache', + groupName: 'lru-cache related packages', packageNames: [ - 'uuid', - '@types/uuid', + 'lru-cache', + '@types/lru-cache', ], }, { - groupSlug: 'vinyl-fs', - groupName: 'vinyl-fs related packages', + groupSlug: 'mapbox-gl', + groupName: 'mapbox-gl related packages', packageNames: [ - 'vinyl-fs', - '@types/vinyl-fs', + 'mapbox-gl', + '@types/mapbox-gl', ], }, { - groupSlug: 'zen-observable', - groupName: 'zen-observable related packages', + groupSlug: 'markdown-it', + groupName: 'markdown-it related packages', packageNames: [ - 'zen-observable', - '@types/zen-observable', + 'markdown-it', + '@types/markdown-it', ], }, { - groupSlug: 'archiver', - groupName: 'archiver related packages', + groupSlug: 'memoize-one', + groupName: 'memoize-one related packages', packageNames: [ - 'archiver', - '@types/archiver', + 'memoize-one', + '@types/memoize-one', ], }, { - groupSlug: 'base64-js', - groupName: 'base64-js related packages', + groupSlug: 'mime', + groupName: 'mime related packages', packageNames: [ - 'base64-js', - '@types/base64-js', + 'mime', + '@types/mime', ], }, { - groupSlug: 'chroma-js', - groupName: 'chroma-js related packages', + groupSlug: 'minimatch', + groupName: 'minimatch related packages', packageNames: [ - 'chroma-js', - '@types/chroma-js', + 'minimatch', + '@types/minimatch', ], }, { - groupSlug: 'color', - groupName: 'color related packages', - packageNames: [ - 'color', - '@types/color', + groupSlug: 'mocha', + groupName: 'mocha related packages', + packagePatterns: [ + '(\\b|_)mocha(\\b|_)', ], }, { - groupSlug: 'cytoscape', - groupName: 'cytoscape related packages', - packageNames: [ - 'cytoscape', - '@types/cytoscape', + groupSlug: 'moment', + groupName: 'moment related packages', + packagePatterns: [ + '(\\b|_)moment(\\b|_)', ], }, { - groupSlug: 'fancy-log', - groupName: 'fancy-log related packages', + groupSlug: 'mustache', + groupName: 'mustache related packages', packageNames: [ - 'fancy-log', - '@types/fancy-log', + 'mustache', + '@types/mustache', ], }, { - groupSlug: 'file-saver', - groupName: 'file-saver related packages', + groupSlug: 'ncp', + groupName: 'ncp related packages', packageNames: [ - 'file-saver', - '@types/file-saver', + 'ncp', + '@types/ncp', ], }, { - groupSlug: 'getos', - groupName: 'getos related packages', + groupSlug: 'nock', + groupName: 'nock related packages', packageNames: [ - 'getos', - '@types/getos', + 'nock', + '@types/nock', ], }, { - groupSlug: 'git-url-parse', - groupName: 'git-url-parse related packages', + groupSlug: 'node', + groupName: 'node related packages', packageNames: [ - 'git-url-parse', - '@types/git-url-parse', + 'node', + '@types/node', ], }, { - groupSlug: 'jsdom', - groupName: 'jsdom related packages', + groupSlug: 'node-fetch', + groupName: 'node-fetch related packages', packageNames: [ - 'jsdom', - '@types/jsdom', + 'node-fetch', + '@types/node-fetch', ], }, { - groupSlug: 'jsonwebtoken', - groupName: 'jsonwebtoken related packages', + groupSlug: 'nodemailer', + groupName: 'nodemailer related packages', packageNames: [ - 'jsonwebtoken', - '@types/jsonwebtoken', + 'nodemailer', + '@types/nodemailer', ], }, { - groupSlug: 'mapbox-gl', - groupName: 'mapbox-gl related packages', + groupSlug: 'object-hash', + groupName: 'object-hash related packages', packageNames: [ - 'mapbox-gl', - '@types/mapbox-gl', + 'object-hash', + '@types/object-hash', ], }, { - groupSlug: 'memoize-one', - groupName: 'memoize-one related packages', + groupSlug: 'opn', + groupName: 'opn related packages', packageNames: [ - 'memoize-one', - '@types/memoize-one', + 'opn', + '@types/opn', ], }, { - groupSlug: 'mime', - groupName: 'mime related packages', + groupSlug: 'ora', + groupName: 'ora related packages', packageNames: [ - 'mime', - '@types/mime', + 'ora', + '@types/ora', ], }, { - groupSlug: 'nock', - groupName: 'nock related packages', + groupSlug: 'papaparse', + groupName: 'papaparse related packages', packageNames: [ - 'nock', - '@types/nock', + 'papaparse', + '@types/papaparse', ], }, { - groupSlug: 'node-fetch', - groupName: 'node-fetch related packages', + groupSlug: 'parse-link-header', + groupName: 'parse-link-header related packages', packageNames: [ - 'node-fetch', - '@types/node-fetch', + 'parse-link-header', + '@types/parse-link-header', ], }, { - groupSlug: 'nodemailer', - groupName: 'nodemailer related packages', + groupSlug: 'pegjs', + groupName: 'pegjs related packages', packageNames: [ - 'nodemailer', - '@types/nodemailer', + 'pegjs', + '@types/pegjs', ], }, { - groupSlug: 'object-hash', - groupName: 'object-hash related packages', + groupSlug: 'pngjs', + groupName: 'pngjs related packages', packageNames: [ - 'object-hash', - '@types/object-hash', + 'pngjs', + '@types/pngjs', ], }, { - groupSlug: 'papaparse', - groupName: 'papaparse related packages', + groupSlug: 'podium', + groupName: 'podium related packages', packageNames: [ - 'papaparse', - '@types/papaparse', + 'podium', + '@types/podium', ], }, { @@ -793,6 +713,35 @@ '@types/puppeteer', ], }, + { + groupSlug: 'react', + groupName: 'react related packages', + packagePatterns: [ + '(\\b|_)react(\\b|_)', + '(\\b|_)redux(\\b|_)', + '(\\b|_)enzyme(\\b|_)', + ], + packageNames: [ + 'ngreact', + '@types/ngreact', + 'recompose', + '@types/recompose', + 'prop-types', + '@types/prop-types', + 'typescript-fsa-reducers', + '@types/typescript-fsa-reducers', + 'reselect', + '@types/reselect', + ], + }, + { + groupSlug: 'read-pkg', + groupName: 'read-pkg related packages', + packageNames: [ + 'read-pkg', + '@types/read-pkg', + ], + }, { groupSlug: 'reduce-reducers', groupName: 'reduce-reducers related packages', @@ -802,123 +751,166 @@ ], }, { - groupSlug: 'tar-fs', - groupName: 'tar-fs related packages', + groupSlug: 'request', + groupName: 'request related packages', packageNames: [ - 'tar-fs', - '@types/tar-fs', + 'request', + '@types/request', ], }, { - groupSlug: 'tinycolor2', - groupName: 'tinycolor2 related packages', + groupSlug: 'selenium-webdriver', + groupName: 'selenium-webdriver related packages', packageNames: [ - 'tinycolor2', - '@types/tinycolor2', + 'selenium-webdriver', + '@types/selenium-webdriver', ], }, { - groupSlug: 'xml-crypto', - groupName: 'xml-crypto related packages', + groupSlug: 'semver', + groupName: 'semver related packages', packageNames: [ - 'xml-crypto', - '@types/xml-crypto', + 'semver', + '@types/semver', ], }, { - groupSlug: 'xml2js', - groupName: 'xml2js related packages', + groupSlug: 'sinon', + groupName: 'sinon related packages', packageNames: [ - 'xml2js', - '@types/xml2js', + 'sinon', + '@types/sinon', ], }, { - groupSlug: 'intl-relativeformat', - groupName: 'intl-relativeformat related packages', + groupSlug: 'storybook', + groupName: 'storybook related packages', + packagePatterns: [ + '(\\b|_)storybook(\\b|_)', + ], + }, + { + groupSlug: 'strip-ansi', + groupName: 'strip-ansi related packages', packageNames: [ - 'intl-relativeformat', - '@types/intl-relativeformat', + 'strip-ansi', + '@types/strip-ansi', ], }, { - groupSlug: 'cmd-shim', - groupName: 'cmd-shim related packages', + groupSlug: 'strong-log-transformer', + groupName: 'strong-log-transformer related packages', packageNames: [ - 'cmd-shim', - '@types/cmd-shim', + 'strong-log-transformer', + '@types/strong-log-transformer', ], }, { - groupSlug: 'cpy', - groupName: 'cpy related packages', + groupSlug: 'styled-components', + groupName: 'styled-components related packages', packageNames: [ - 'cpy', - '@types/cpy', + 'styled-components', + '@types/styled-components', ], }, { - groupSlug: 'indent-string', - groupName: 'indent-string related packages', + groupSlug: 'supertest', + groupName: 'supertest related packages', packageNames: [ - 'indent-string', - '@types/indent-string', + 'supertest', + '@types/supertest', ], }, { - groupSlug: 'lodash.clonedeepwith', - groupName: 'lodash.clonedeepwith related packages', + groupSlug: 'supertest-as-promised', + groupName: 'supertest-as-promised related packages', packageNames: [ - 'lodash.clonedeepwith', - '@types/lodash.clonedeepwith', + 'supertest-as-promised', + '@types/supertest-as-promised', ], }, { - groupSlug: 'log-symbols', - groupName: 'log-symbols related packages', + groupSlug: 'tar-fs', + groupName: 'tar-fs related packages', packageNames: [ - 'log-symbols', - '@types/log-symbols', + 'tar-fs', + '@types/tar-fs', ], }, { - groupSlug: 'ncp', - groupName: 'ncp related packages', + groupSlug: 'tempy', + groupName: 'tempy related packages', packageNames: [ - 'ncp', - '@types/ncp', + 'tempy', + '@types/tempy', ], }, { - groupSlug: 'ora', - groupName: 'ora related packages', + groupSlug: 'tinycolor2', + groupName: 'tinycolor2 related packages', packageNames: [ - 'ora', - '@types/ora', + 'tinycolor2', + '@types/tinycolor2', ], }, { - groupSlug: 'read-pkg', - groupName: 'read-pkg related packages', + groupSlug: 'type-detect', + groupName: 'type-detect related packages', packageNames: [ - 'read-pkg', - '@types/read-pkg', + 'type-detect', + '@types/type-detect', ], }, { - groupSlug: 'strong-log-transformer', - groupName: 'strong-log-transformer related packages', + groupSlug: 'typescript', + groupName: 'typescript related packages', + packagePatterns: [ + '(\\b|_)ts(\\b|_)', + '(\\b|_)typescript(\\b|_)', + ], packageNames: [ - 'strong-log-transformer', - '@types/strong-log-transformer', + 'tslib', + '@types/tslib', ], }, { - groupSlug: 'tempy', - groupName: 'tempy related packages', + groupSlug: 'uuid', + groupName: 'uuid related packages', packageNames: [ - 'tempy', - '@types/tempy', + 'uuid', + '@types/uuid', + ], + }, + { + groupSlug: 'vega', + groupName: 'vega related packages', + packagePatterns: [ + '(\\b|_)vega(\\b|_)', + ], + enabled: false, + }, + { + groupSlug: 'vinyl-fs', + groupName: 'vinyl-fs related packages', + packageNames: [ + 'vinyl-fs', + '@types/vinyl-fs', + ], + }, + { + groupSlug: 'webpack', + groupName: 'webpack related packages', + packagePatterns: [ + '(\\b|_)webpack(\\b|_)', + '(\\b|_)loader(\\b|_)', + '(\\b|_)acorn(\\b|_)', + '(\\b|_)terser(\\b|_)', + ], + packageNames: [ + 'mini-css-extract-plugin', + '@types/mini-css-extract-plugin', + 'chokidar', + '@types/chokidar', ], }, { @@ -938,11 +930,27 @@ ], }, { - groupSlug: 'parse-link-header', - groupName: 'parse-link-header related packages', + groupSlug: 'xml-crypto', + groupName: 'xml-crypto related packages', packageNames: [ - 'parse-link-header', - '@types/parse-link-header', + 'xml-crypto', + '@types/xml-crypto', + ], + }, + { + groupSlug: 'xml2js', + groupName: 'xml2js related packages', + packageNames: [ + 'xml2js', + '@types/xml2js', + ], + }, + { + groupSlug: 'zen-observable', + groupName: 'zen-observable related packages', + packageNames: [ + 'zen-observable', + '@types/zen-observable', ], }, { diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index 4472891e580fb..fc88f2657018f 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -17,12 +17,23 @@ * under the License. */ -require('../src/setup_node_env'); -require('@kbn/test').runTestsCli([ +// eslint-disable-next-line no-restricted-syntax +const alwaysImportedTests = [ require.resolve('../test/functional/config.js'), - require.resolve('../test/api_integration/config.js'), require.resolve('../test/plugin_functional/config.js'), - require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), +]; +// eslint-disable-next-line no-restricted-syntax +const onlyNotInCoverageTests = [ + require.resolve('../test/api_integration/config.js'), + require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/examples/config.js'), +]; + +require('../src/setup_node_env'); +require('@kbn/test').runTestsCli([ + // eslint-disable-next-line no-restricted-syntax + ...alwaysImportedTests, + // eslint-disable-next-line no-restricted-syntax + ...(!!process.env.CODE_COVERAGE ? [] : onlyNotInCoverageTests), ]); diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 1c78de966c46f..b70ac610f24a7 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1194,6 +1194,7 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | | `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | +| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.createClient`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md) | | | `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | | | `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | | | `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | | diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index 281778f9420dd..cafc7e5887e38 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -279,6 +279,37 @@ describe('PluginsService', () => { expect((contracts.get('pluginA')! as any).setupValue).toEqual(1); expect((contracts.get('pluginB')! as any).pluginAPlusB).toEqual(2); }); + + describe('timeout', () => { + const flushPromises = () => new Promise(resolve => setImmediate(resolve)); + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + + it('throws timeout error if "setup" was not completed in 30 sec.', async () => { + mockPluginInitializers.set( + 'pluginA', + jest.fn(() => ({ + setup: jest.fn(() => new Promise(i => i)), + start: jest.fn(() => ({ value: 1 })), + stop: jest.fn(), + })) + ); + const pluginsService = new PluginsService(mockCoreContext, plugins); + const promise = pluginsService.setup(mockSetupDeps); + + jest.runAllTimers(); // load plugin bundles + await flushPromises(); + jest.runAllTimers(); // setup plugins + + await expect(promise).rejects.toMatchInlineSnapshot( + `[Error: Setup lifecycle of "pluginA" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.]` + ); + }); + }); }); describe('#start()', () => { @@ -331,6 +362,34 @@ describe('PluginsService', () => { expect((contracts.get('pluginA')! as any).startValue).toEqual(2); expect((contracts.get('pluginB')! as any).pluginAPlusB).toEqual(3); }); + describe('timeout', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + + it('throws timeout error if "start" was not completed in 30 sec.', async () => { + mockPluginInitializers.set( + 'pluginA', + jest.fn(() => ({ + setup: jest.fn(() => ({ value: 1 })), + start: jest.fn(() => new Promise(i => i)), + stop: jest.fn(), + })) + ); + const pluginsService = new PluginsService(mockCoreContext, plugins); + await pluginsService.setup(mockSetupDeps); + + const promise = pluginsService.start(mockStartDeps); + jest.runAllTimers(); + + await expect(promise).rejects.toMatchInlineSnapshot( + `[Error: Start lifecycle of "pluginA" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.]` + ); + }); + }); }); describe('#stop()', () => { diff --git a/src/core/public/plugins/plugins_service.ts b/src/core/public/plugins/plugins_service.ts index c1939a3397647..8e1574d05baf8 100644 --- a/src/core/public/plugins/plugins_service.ts +++ b/src/core/public/plugins/plugins_service.ts @@ -28,7 +28,9 @@ import { } from './plugin_context'; import { InternalCoreSetup, InternalCoreStart } from '../core_system'; import { InjectedPluginMetadata } from '../injected_metadata'; +import { withTimeout } from '../../utils'; +const Sec = 1000; /** @internal */ export type PluginsServiceSetupDeps = InternalCoreSetup; /** @internal */ @@ -110,13 +112,15 @@ export class PluginsService implements CoreService ); - contracts.set( - pluginName, - await plugin.setup( + const contract = await withTimeout({ + promise: plugin.setup( createPluginSetupContext(this.coreContext, deps, plugin), pluginDepContracts - ) - ); + ), + timeout: 30 * Sec, + errorMessage: `Setup lifecycle of "${pluginName}" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.`, + }); + contracts.set(pluginName, contract); this.satupPlugins.push(pluginName); } @@ -142,13 +146,15 @@ export class PluginsService implements CoreService ); - contracts.set( - pluginName, - await plugin.start( + const contract = await withTimeout({ + promise: plugin.start( createPluginStartContext(this.coreContext, deps, plugin), pluginDepContracts - ) - ); + ), + timeout: 30 * Sec, + errorMessage: `Start lifecycle of "${pluginName}" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.`, + }); + contracts.set(pluginName, contract); } // Expose start contracts diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/cluster_client.ts index d43ab9d546ed2..2352677b8d3e0 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/cluster_client.ts @@ -89,15 +89,35 @@ export interface FakeRequest { } /** - * 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(...)`). + * Represents an Elasticsearch cluster API client created by the platform. + * It 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 {@link ClusterClient}. * * @public */ -export type IClusterClient = Pick; +export type IClusterClient = Pick; + +/** + * Represents an Elasticsearch cluster API client created by a plugin. + * It 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 {@link ClusterClient}. + * + * @public + */ +export type ICustomClusterClient = Pick; + +/** + A user credentials container. + * It accommodates the necessary auth credentials to impersonate the current user. + * + * @public + * See {@link KibanaRequest}. + */ +export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; /** * {@inheritDoc IClusterClient} @@ -174,7 +194,7 @@ export class ClusterClient implements IClusterClient { * @param request - Request the `IScopedClusterClient` instance will be scoped to. * Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform */ - public asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient { + public asScoped(request?: ScopeableRequest): IScopedClusterClient { // It'd have been quite expensive to create and configure client for every incoming // request since it involves parsing of the config, reading of the SSL certificate and // key files etc. Moreover scoped client needs two Elasticsearch JS clients at the same diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index d935d1a66eccf..1b52f22c4da09 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -18,33 +18,67 @@ */ import { BehaviorSubject } from 'rxjs'; -import { IClusterClient } from './cluster_client'; +import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { IScopedClusterClient } from './scoped_cluster_client'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; -import { InternalElasticsearchServiceSetup } from './types'; +import { InternalElasticsearchServiceSetup, ElasticsearchServiceSetup } from './types'; const createScopedClusterClientMock = (): jest.Mocked => ({ callAsInternalUser: jest.fn(), callAsCurrentUser: jest.fn(), }); -const createClusterClientMock = (): jest.Mocked => ({ - callAsInternalUser: jest.fn(), - asScoped: jest.fn().mockImplementation(createScopedClusterClientMock), +const createCustomClusterClientMock = (): jest.Mocked => ({ + ...createClusterClientMock(), close: jest.fn(), }); +function createClusterClientMock() { + const client: jest.Mocked = { + callAsInternalUser: jest.fn(), + asScoped: jest.fn(), + }; + client.asScoped.mockReturnValue(createScopedClusterClientMock()); + return client; +} + +type MockedElasticSearchServiceSetup = jest.Mocked< + ElasticsearchServiceSetup & { + adminClient: jest.Mocked; + dataClient: jest.Mocked; + } +>; + const createSetupContractMock = () => { - const setupContract: jest.Mocked = { + const setupContract: MockedElasticSearchServiceSetup = { + createClient: jest.fn(), + adminClient: createClusterClientMock(), + dataClient: createClusterClientMock(), + }; + setupContract.createClient.mockReturnValue(createCustomClusterClientMock()); + setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + return setupContract; +}; + +type MockedInternalElasticSearchServiceSetup = jest.Mocked< + InternalElasticsearchServiceSetup & { + adminClient: jest.Mocked; + dataClient: jest.Mocked; + } +>; +const createInternalSetupContractMock = () => { + const setupContract: MockedInternalElasticSearchServiceSetup = { + ...createSetupContractMock(), legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), }, - - createClient: jest.fn().mockImplementation(createClusterClientMock), - adminClient$: new BehaviorSubject((createClusterClientMock() as unknown) as IClusterClient), - dataClient$: new BehaviorSubject((createClusterClientMock() as unknown) as IClusterClient), + adminClient$: new BehaviorSubject(createClusterClientMock()), + dataClient$: new BehaviorSubject(createClusterClientMock()), }; + setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); return setupContract; }; @@ -55,14 +89,16 @@ const createMock = () => { start: jest.fn(), stop: jest.fn(), }; - mocked.setup.mockResolvedValue(createSetupContractMock()); + mocked.setup.mockResolvedValue(createInternalSetupContractMock()); mocked.stop.mockResolvedValue(); return mocked; }; export const elasticsearchServiceMock = { create: createMock, - createSetupContract: createSetupContractMock, + createInternalSetup: createInternalSetupContractMock, + createSetup: createSetupContractMock, createClusterClient: createClusterClientMock, + createCustomClusterClient: createCustomClusterClientMock, createScopedClusterClient: createScopedClusterClientMock, }; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 6c4a1f263bc71..aefc8b1fdf047 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -30,6 +30,7 @@ import { loggingServiceMock } from '../logging/logging_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; +import { elasticsearchServiceMock } from './elasticsearch_service.mock'; let elasticsearchService: ElasticsearchService; const configService = configServiceMock.create(); @@ -69,6 +70,27 @@ describe('#setup', () => { ); }); + it('returns data and admin client as a part of the contract', async () => { + const mockAdminClusterClientInstance = elasticsearchServiceMock.createClusterClient(); + const mockDataClusterClientInstance = elasticsearchServiceMock.createClusterClient(); + MockClusterClient.mockImplementationOnce( + () => mockAdminClusterClientInstance + ).mockImplementationOnce(() => mockDataClusterClientInstance); + + const setupContract = await elasticsearchService.setup(deps); + + const adminClient = setupContract.adminClient; + const dataClient = setupContract.dataClient; + + expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await adminClient.callAsInternalUser('any'); + expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + + expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await dataClient.callAsInternalUser('any'); + expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + }); + it('returns data and admin client observables as a part of the contract', async () => { const mockAdminClusterClientInstance = { close: jest.fn() }; const mockDataClusterClientInstance = { close: jest.fn() }; diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index be0a817c54146..de32e7f6cf225 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -18,17 +18,18 @@ */ import { ConnectableObservable, Observable, Subscription } from 'rxjs'; -import { filter, first, map, publishReplay, switchMap } from 'rxjs/operators'; +import { filter, first, map, publishReplay, switchMap, take } from 'rxjs/operators'; import { CoreService } from '../../types'; import { merge } from '../../utils'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { ClusterClient } from './cluster_client'; +import { ClusterClient, ScopeableRequest } from './cluster_client'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; import { InternalElasticsearchServiceSetup } from './types'; +import { CallAPIOptions } from './api_types'; /** @internal */ interface CoreClusterClients { @@ -94,11 +95,67 @@ export class ElasticsearchService implements CoreService clients.adminClient)); + const dataClient$ = clients$.pipe(map(clients => clients.dataClient)); + + const adminClient = { + async callAsInternalUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await adminClient$.pipe(take(1)).toPromise(); + return await client.callAsInternalUser(endpoint, clientParams, options); + }, + asScoped(request: ScopeableRequest) { + return { + callAsInternalUser: adminClient.callAsInternalUser, + async callAsCurrentUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await adminClient$.pipe(take(1)).toPromise(); + return await client + .asScoped(request) + .callAsCurrentUser(endpoint, clientParams, options); + }, + }; + }, + }; + const dataClient = { + async callAsInternalUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await dataClient$.pipe(take(1)).toPromise(); + return await client.callAsInternalUser(endpoint, clientParams, options); + }, + asScoped(request: ScopeableRequest) { + return { + callAsInternalUser: dataClient.callAsInternalUser, + async callAsCurrentUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await dataClient$.pipe(take(1)).toPromise(); + return await client + .asScoped(request) + .callAsCurrentUser(endpoint, clientParams, options); + }, + }; + }, + }; + return { legacy: { config$: clients$.pipe(map(clients => clients.config)) }, - adminClient$: clients$.pipe(map(clients => clients.adminClient)), - dataClient$: clients$.pipe(map(clients => clients.dataClient)), + adminClient$, + dataClient$, + adminClient, + dataClient, createClient: (type: string, clientConfig: Partial = {}) => { const finalConfig = merge({}, config, clientConfig); diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index 1f99f86d9887b..5d64fadfaa184 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -18,7 +18,13 @@ */ export { ElasticsearchService } from './elasticsearch_service'; -export { IClusterClient, ClusterClient, FakeRequest } from './cluster_client'; +export { + ClusterClient, + FakeRequest, + IClusterClient, + ICustomClusterClient, + ScopeableRequest, +} from './cluster_client'; export { IScopedClusterClient, ScopedClusterClient, Headers } from './scoped_cluster_client'; export { ElasticsearchClientConfig } from './elasticsearch_client_config'; export { config } from './elasticsearch_config'; diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index 505b57c7c9e8e..22340bf3f2fc6 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -20,7 +20,7 @@ import { Observable } from 'rxjs'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; -import { IClusterClient } from './cluster_client'; +import { IClusterClient, ICustomClusterClient } from './cluster_client'; /** * @public @@ -46,29 +46,29 @@ export interface ElasticsearchServiceSetup { readonly createClient: ( type: string, clientConfig?: Partial - ) => IClusterClient; + ) => ICustomClusterClient; /** - * Observable of clients for the `admin` cluster. Observable emits when Elasticsearch config changes on the Kibana - * server. See {@link IClusterClient}. + * A client for the `admin` cluster. All Elasticsearch config value changes are processed under the hood. + * See {@link IClusterClient}. * - * @exmaple + * @example * ```js - * const client = await elasticsearch.adminClient$.pipe(take(1)).toPromise(); + * const client = core.elasticsearch.adminClient; * ``` */ - readonly adminClient$: Observable; + readonly adminClient: IClusterClient; /** - * Observable of clients for the `data` cluster. Observable emits when Elasticsearch config changes on the Kibana - * server. See {@link IClusterClient}. + * A client for the `data` cluster. All Elasticsearch config value changes are processed under the hood. + * See {@link IClusterClient}. * - * @exmaple + * @example * ```js - * const client = await elasticsearch.dataClient$.pipe(take(1)).toPromise(); + * const client = core.elasticsearch.dataClient; * ``` */ - readonly dataClient$: Observable; + readonly dataClient: IClusterClient; } /** @internal */ @@ -77,4 +77,7 @@ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceS readonly legacy: { readonly config$: Observable; }; + + readonly adminClient$: Observable; + readonly dataClient$: Observable; } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 953fa0738597c..eccf3985fc495 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -74,6 +74,7 @@ export { CspConfig, ICspConfig } from './csp'; export { ClusterClient, IClusterClient, + ICustomClusterClient, Headers, ScopedClusterClient, IScopedClusterClient, @@ -83,6 +84,7 @@ export { ElasticsearchServiceSetup, APICaller, FakeRequest, + ScopeableRequest, } from './elasticsearch'; export * from './elasticsearch/api_types'; export { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index e17de7364ce59..cc36b90ec526d 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -249,8 +249,8 @@ export class LegacyService implements CoreService { capabilities: setupDeps.core.capabilities, context: setupDeps.core.context, elasticsearch: { - adminClient$: setupDeps.core.elasticsearch.adminClient$, - dataClient$: setupDeps.core.elasticsearch.dataClient$, + adminClient: setupDeps.core.elasticsearch.adminClient, + dataClient: setupDeps.core.elasticsearch.dataClient, createClient: setupDeps.core.elasticsearch.createClient, }, http: { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 53849b040c413..073d380d3aa67 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -107,7 +107,7 @@ function createCoreSetupMock() { const mock: MockedKeys = { capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), http: httpMock, savedObjects: savedObjectsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, @@ -131,7 +131,7 @@ function createInternalCoreSetupMock() { const setupDeps: InternalCoreSetup = { capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createInternalSetup(), http: httpServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), savedObjects: savedObjectsServiceMock.createSetupContract(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 6e9a7967e9eca..6d82a8d3ec6cf 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -145,8 +145,8 @@ export function createPluginSetupContext( createContextContainer: deps.context.createContextContainer, }, elasticsearch: { - adminClient$: deps.elasticsearch.adminClient$, - dataClient$: deps.elasticsearch.dataClient$, + adminClient: deps.elasticsearch.adminClient, + dataClient: deps.elasticsearch.dataClient, createClient: deps.elasticsearch.createClient, }, http: { diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 18c04af3bb641..22dfbeecbaedd 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -414,3 +414,51 @@ test('`startPlugins` only starts plugins that were setup', async () => { ] `); }); + +describe('setup', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + it('throws timeout error if "setup" was not completed in 30 sec.', async () => { + const plugin: PluginWrapper = createPlugin('timeout-setup'); + jest.spyOn(plugin, 'setup').mockImplementation(() => new Promise(i => i)); + pluginsSystem.addPlugin(plugin); + mockCreatePluginSetupContext.mockImplementation(() => ({})); + + const promise = pluginsSystem.setupPlugins(setupDeps); + jest.runAllTimers(); + + await expect(promise).rejects.toMatchInlineSnapshot( + `[Error: Setup lifecycle of "timeout-setup" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.]` + ); + }); +}); + +describe('start', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + it('throws timeout error if "start" was not completed in 30 sec.', async () => { + const plugin: PluginWrapper = createPlugin('timeout-start'); + jest.spyOn(plugin, 'setup').mockResolvedValue({}); + jest.spyOn(plugin, 'start').mockImplementation(() => new Promise(i => i)); + + pluginsSystem.addPlugin(plugin); + mockCreatePluginSetupContext.mockImplementation(() => ({})); + mockCreatePluginStartContext.mockImplementation(() => ({})); + + await pluginsSystem.setupPlugins(setupDeps); + const promise = pluginsSystem.startPlugins(startDeps); + jest.runAllTimers(); + + await expect(promise).rejects.toMatchInlineSnapshot( + `[Error: Start lifecycle of "timeout-start" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.]` + ); + }); +}); diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index f437b51e5b07a..dd2df7c8e01d1 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -23,7 +23,9 @@ import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, PluginName, PluginOpaqueId } from './types'; import { createPluginSetupContext, createPluginStartContext } from './plugin_context'; import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; +import { withTimeout } from '../../utils'; +const Sec = 1000; /** @internal */ export class PluginsSystem { private readonly plugins = new Map(); @@ -85,14 +87,16 @@ export class PluginsSystem { return depContracts; }, {} as Record); - contracts.set( - pluginName, - await plugin.setup( + const contract = await withTimeout({ + promise: plugin.setup( createPluginSetupContext(this.coreContext, deps, plugin), pluginDepContracts - ) - ); + ), + timeout: 30 * Sec, + errorMessage: `Setup lifecycle of "${pluginName}" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.`, + }); + contracts.set(pluginName, contract); this.satupPlugins.push(pluginName); } @@ -121,13 +125,16 @@ export class PluginsSystem { return depContracts; }, {} as Record); - contracts.set( - pluginName, - await plugin.start( + const contract = await withTimeout({ + promise: plugin.start( createPluginStartContext(this.coreContext, deps, plugin), pluginDepContracts - ) - ); + ), + timeout: 30 * Sec, + errorMessage: `Start lifecycle of "${pluginName}" plugin wasn't completed in 30sec. Consider disabling the plugin and re-start.`, + }); + + contracts.set(pluginName, contract); } return contracts; diff --git a/src/core/server/rendering/views/styles.tsx b/src/core/server/rendering/views/styles.tsx index f41627bcfe07f..dfcb4213d90f7 100644 --- a/src/core/server/rendering/views/styles.tsx +++ b/src/core/server/rendering/views/styles.tsx @@ -28,8 +28,6 @@ interface Props { } export const Styles: FunctionComponent = ({ darkMode }) => { - const themeBackground = darkMode ? '#25262e' : '#f5f7fa'; - return (